changeset 0:30bb7074acb1

upload all file of tighVNCProxy
author e085711
date Tue, 12 Apr 2011 12:58:05 +0900
parents
children cbe9c4ac0369
files .project src/AuthPanel.java src/ButtonPanel.java src/CapabilityInfo.java src/CapsContainer.java src/ClipboardFrame.java src/DesCipher.java src/HTTPConnectSocket.java src/HTTPConnectSocketFactory.java src/InStream.java src/MemInStream.java src/OptionsFrame.java src/RecordingFrame.java src/ReloginPanel.java src/RfbProto.java src/SessionRecorder.java src/SocketFactory.java src/VncCanvas.java src/VncCanvas2.java src/VncViewer.java src/ZlibInStream.java
diffstat 21 files changed, 7023 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.project	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>tightVNCProxy</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+	</buildSpec>
+	<natures>
+	</natures>
+</projectDescription>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/AuthPanel.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,116 @@
+//
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//  Copyright (C) 2002-2006 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+
+//
+// The panel which implements the user authentication scheme
+//
+
+class AuthPanel extends Panel implements ActionListener {
+
+  TextField passwordField;
+  Button okButton;
+
+  //
+  // Constructor.
+  //
+
+  public AuthPanel(VncViewer viewer)
+  {
+    Label titleLabel = new Label("VNC Authentication", Label.CENTER);
+    titleLabel.setFont(new Font("Helvetica", Font.BOLD, 18));
+
+    Label promptLabel = new Label("Password:", Label.CENTER);
+
+    passwordField = new TextField(10);
+    passwordField.setForeground(Color.black);
+    passwordField.setBackground(Color.white);
+    passwordField.setEchoChar('*');
+
+    okButton = new Button("OK");
+
+    GridBagLayout gridbag = new GridBagLayout();
+    GridBagConstraints gbc = new GridBagConstraints();
+
+    setLayout(gridbag);
+
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.insets = new Insets(0,0,20,0);
+    gridbag.setConstraints(titleLabel,gbc);
+    add(titleLabel);
+
+    gbc.fill = GridBagConstraints.NONE;
+    gbc.gridwidth = 1;
+    gbc.insets = new Insets(0,0,0,0);
+    gridbag.setConstraints(promptLabel,gbc);
+    add(promptLabel);
+
+    gridbag.setConstraints(passwordField,gbc);
+    add(passwordField);
+    passwordField.addActionListener(this);
+
+    // gbc.ipady = 10;
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.insets = new Insets(0,20,0,0);
+    gbc.ipadx = 30;
+    gridbag.setConstraints(okButton,gbc);
+    add(okButton);
+    okButton.addActionListener(this);
+  }
+
+  //
+  // Move keyboard focus to the default object, that is, the password
+  // text field.
+  //
+
+  public void moveFocusToDefaultField()
+  {
+    passwordField.requestFocus();
+  }
+
+  //
+  // This method is called when a button is pressed or return is
+  // pressed in the password text field.
+  //
+
+  public synchronized void actionPerformed(ActionEvent evt)
+  {
+    if (evt.getSource() == passwordField || evt.getSource() == okButton) {
+      passwordField.setEnabled(false);
+      notify();
+    }
+  }
+
+  //
+  // Wait for user entering a password, and return it as String.
+  //
+
+  public synchronized String getPassword() throws Exception
+  {
+    try {
+      wait();
+    } catch (InterruptedException e) { }
+    return passwordField.getText();
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ButtonPanel.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,154 @@
+//
+//  Copyright (C) 2001,2002 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// ButtonPanel class implements panel with four buttons in the
+// VNCViewer desktop window.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+
+class ButtonPanel extends Panel implements ActionListener {
+
+  VncViewer viewer;
+  Button disconnectButton;
+  Button optionsButton;
+  Button recordButton;
+  Button clipboardButton;
+  Button ctrlAltDelButton;
+  Button refreshButton;
+
+  ButtonPanel(VncViewer v) {
+    viewer = v;
+
+    setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+    disconnectButton = new Button("Disconnect");
+    disconnectButton.setEnabled(false);
+    add(disconnectButton);
+    disconnectButton.addActionListener(this);
+    optionsButton = new Button("Options");
+    add(optionsButton);
+    optionsButton.addActionListener(this);
+    clipboardButton = new Button("Clipboard");
+    clipboardButton.setEnabled(false);
+    add(clipboardButton);
+    clipboardButton.addActionListener(this);
+    if (viewer.rec != null) {
+      recordButton = new Button("Record");
+      add(recordButton);
+      recordButton.addActionListener(this);
+    }
+    ctrlAltDelButton = new Button("Send Ctrl-Alt-Del");
+    ctrlAltDelButton.setEnabled(false);
+    add(ctrlAltDelButton);
+    ctrlAltDelButton.addActionListener(this);
+    refreshButton = new Button("Refresh");
+    refreshButton.setEnabled(false);
+    add(refreshButton);
+    refreshButton.addActionListener(this);
+  }
+
+  //
+  // Enable buttons on successful connection.
+  //
+
+  public void enableButtons() {
+    disconnectButton.setEnabled(true);
+    clipboardButton.setEnabled(true);
+    refreshButton.setEnabled(true);
+  }
+
+  //
+  // Disable all buttons on disconnect.
+  //
+
+  public void disableButtonsOnDisconnect() {
+    remove(disconnectButton);
+    disconnectButton = new Button("Hide desktop");
+    disconnectButton.setEnabled(true);
+    add(disconnectButton, 0);
+    disconnectButton.addActionListener(this);
+
+    optionsButton.setEnabled(false);
+    clipboardButton.setEnabled(false);
+    ctrlAltDelButton.setEnabled(false);
+    refreshButton.setEnabled(false);
+
+    validate();
+  }
+
+  //
+  // Enable/disable controls that should not be available in view-only
+  // mode.
+  //
+
+  public void enableRemoteAccessControls(boolean enable) {
+    ctrlAltDelButton.setEnabled(enable);
+  }
+
+  //
+  // Event processing.
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+
+    viewer.moveFocusToDesktop();
+
+    if (evt.getSource() == disconnectButton) {
+      viewer.disconnect();
+
+    } else if (evt.getSource() == optionsButton) {
+      viewer.options.setVisible(!viewer.options.isVisible());
+
+    } else if (evt.getSource() == recordButton) {
+      viewer.rec.setVisible(!viewer.rec.isVisible());
+
+    } else if (evt.getSource() == clipboardButton) {
+      viewer.clipboard.setVisible(!viewer.clipboard.isVisible());
+
+    } else if (evt.getSource() == ctrlAltDelButton) {
+      try {
+        final int modifiers = InputEvent.CTRL_MASK | InputEvent.ALT_MASK;
+
+        KeyEvent ctrlAltDelEvent =
+          new KeyEvent(this, KeyEvent.KEY_PRESSED, 0, modifiers, 127);
+        viewer.rfb.writeKeyEvent(ctrlAltDelEvent);
+
+        ctrlAltDelEvent =
+          new KeyEvent(this, KeyEvent.KEY_RELEASED, 0, modifiers, 127);
+        viewer.rfb.writeKeyEvent(ctrlAltDelEvent);
+
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    } else if (evt.getSource() == refreshButton) {
+      try {
+	RfbProto rfb = viewer.rfb;
+	rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+					  rfb.framebufferHeight, false);
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CapabilityInfo.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,87 @@
+//
+//  Copyright (C) 2003 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// CapabilityInfo.java - A class to hold information about a
+// particular capability as used in the RFB protocol 3.130.
+//
+
+class CapabilityInfo {
+
+  // Public methods
+
+  public CapabilityInfo(int code,
+			String vendorSignature,
+			String nameSignature,
+			String description) {
+    this.code = code;
+    this.vendorSignature = vendorSignature;
+    this.nameSignature = nameSignature;
+    this.description = description;
+    enabled = false;
+  }
+
+  public CapabilityInfo(int code,
+			byte[] vendorSignature,
+			byte[] nameSignature) {
+    this.code = code;
+    this.vendorSignature = new String(vendorSignature);
+    this.nameSignature = new String(nameSignature);
+    this.description = null;
+    enabled = false;
+  }
+
+  public int getCode() {
+    return code;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void enable() {
+    enabled = true;
+  }
+
+  public boolean equals(CapabilityInfo other) {
+    return (other != null && this.code == other.code &&
+	    this.vendorSignature.equals(other.vendorSignature) &&
+	    this.nameSignature.equals(other.nameSignature));
+  }
+
+  public boolean enableIfEquals(CapabilityInfo other) {
+    if (this.equals(other))
+      enable();
+
+    return isEnabled();
+  }
+
+  // Protected data
+
+  protected int code;
+  protected String vendorSignature;
+  protected String nameSignature;
+
+  protected String description;
+  protected boolean enabled;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CapsContainer.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,103 @@
+//
+//  Copyright (C) 2003 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// CapsContainer.java - A container of capabilities as used in the RFB
+// protocol 3.130
+//
+
+import java.util.Vector;
+import java.util.Hashtable;
+
+class CapsContainer {
+
+  // Public methods
+
+  public CapsContainer() {
+    infoMap = new Hashtable(64, (float)0.25);
+    orderedList = new Vector(32, 8);
+  }
+
+  public void add(CapabilityInfo capinfo) {
+    Integer key = new Integer(capinfo.getCode());
+    infoMap.put(key, capinfo);
+  }
+
+  public void add(int code, String vendor, String name, String desc) {
+    Integer key = new Integer(code);
+    infoMap.put(key, new CapabilityInfo(code, vendor, name, desc));
+  }
+
+  public boolean isKnown(int code) {
+    return infoMap.containsKey(new Integer(code));
+  }
+
+  public CapabilityInfo getInfo(int code) {
+    return (CapabilityInfo)infoMap.get(new Integer(code));
+  }
+
+  public String getDescription(int code) {
+    CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+    if (capinfo == null)
+      return null;
+
+    return capinfo.getDescription();
+  }
+
+  public boolean enable(CapabilityInfo other) {
+    Integer key = new Integer(other.getCode());
+    CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(key);
+    if (capinfo == null)
+      return false;
+
+    boolean enabled = capinfo.enableIfEquals(other);
+    if (enabled)
+      orderedList.addElement(key);
+
+    return enabled;
+  }
+
+  public boolean isEnabled(int code) {
+    CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code));
+    if (capinfo == null)
+      return false;
+
+    return capinfo.isEnabled();
+  }
+
+  public int numEnabled() {
+    return orderedList.size();
+  }
+
+  public int getByOrder(int idx) {
+    int code;
+    try {
+      code = ((Integer)orderedList.elementAt(idx)).intValue();
+    } catch (ArrayIndexOutOfBoundsException e) {
+      code = 0;
+    }
+    return code;
+  }
+
+  // Protected data
+
+  protected Hashtable infoMap;
+  protected Vector orderedList;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ClipboardFrame.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,133 @@
+//
+//  Copyright (C) 2001 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// Clipboard frame.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+
+class ClipboardFrame extends Frame
+  implements WindowListener, ActionListener {
+
+  TextArea textArea;
+  Button clearButton, closeButton;
+  String selection;
+  VncViewer viewer;
+
+  //
+  // Constructor.
+  //
+
+  ClipboardFrame(VncViewer v) {
+    super("TightVNC Clipboard");
+
+    viewer = v;
+
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weighty = 1.0;
+
+    textArea = new TextArea(5, 40);
+    gridbag.setConstraints(textArea, gbc);
+    add(textArea);
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weightx = 1.0;
+    gbc.weighty = 0.0;
+    gbc.gridwidth = 1;
+
+    clearButton = new Button("Clear");
+    gridbag.setConstraints(clearButton, gbc);
+    add(clearButton);
+    clearButton.addActionListener(this);
+
+    closeButton = new Button("Close");
+    gridbag.setConstraints(closeButton, gbc);
+    add(closeButton);
+    closeButton.addActionListener(this);
+
+    pack();
+
+    addWindowListener(this);
+  }
+
+
+  //
+  // Set the cut text from the RFB server.
+  //
+
+  void setCutText(String text) {
+    selection = text;
+    textArea.setText(text);
+    if (isVisible()) {
+      textArea.selectAll();
+    }
+  }
+
+
+  //
+  // When the focus leaves the window, see if we have new cut text and
+  // if so send it to the RFB server.
+  //
+
+  public void windowDeactivated (WindowEvent evt) {
+    if (selection != null && !selection.equals(textArea.getText())) {
+      selection = textArea.getText();
+      viewer.setCutText(selection);
+    }
+  }
+
+  //
+  // Close our window properly.
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    setVisible(false);
+  }
+
+  //
+  // Ignore window events we're not interested in.
+  //
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+
+
+  //
+  // Respond to button presses
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+    if (evt.getSource() == clearButton) {
+      textArea.setText("");
+    } else if (evt.getSource() == closeButton) {
+      setVisible(false);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/DesCipher.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,496 @@
+//
+// This DES class has been extracted from package Acme.Crypto for use in VNC.
+// The bytebit[] array has been reversed so that the most significant bit
+// in each byte of the key is ignored, not the least significant.  Also the
+// unnecessary odd parity code has been removed.
+//
+// These changes are:
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+// This software is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+//
+
+// DesCipher - the DES encryption method
+//
+// The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
+//
+// Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+//
+// Permission to use, copy, modify, and distribute this software
+// and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+// without fee is hereby granted, provided that this copyright notice is kept 
+// intact. 
+// 
+// WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+// OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+// FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+// DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+// 
+// THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+// CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+// PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+// NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+// SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+// SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+// PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  WIDGET WORKSHOP
+// SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+// HIGH RISK ACTIVITIES.
+//
+//
+// The rest is:
+//
+// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+// SUCH DAMAGE.
+//
+// Visit the ACME Labs Java page for up-to-date versions of this and other
+// fine Java utilities: http://www.acme.com/java/
+
+
+import java.io.*;
+
+/// The DES encryption method.
+// <P>
+// This is surprisingly fast, for pure Java.  On a SPARC 20, wrapped
+// in Acme.Crypto.EncryptedOutputStream or Acme.Crypto.EncryptedInputStream,
+// it does around 7000 bytes/second.
+// <P>
+// Most of this code is by Dave Zimmerman <dzimm@widget.com>, and is
+// Copyright (c) 1996 Widget Workshop, Inc.  See the source file for details.
+// <P>
+// <A HREF="/resources/classes/Acme/Crypto/DesCipher.java">Fetch the software.</A><BR>
+// <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
+// <P>
+// @see Des3Cipher
+// @see EncryptedOutputStream
+// @see EncryptedInputStream
+
+public class DesCipher
+    {
+
+    // Constructor, byte-array key.
+    public DesCipher( byte[] key )
+	{
+	setKey( key );
+	}
+
+    // Key routines.
+
+    private int[] encryptKeys = new int[32];
+    private int[] decryptKeys = new int[32];
+
+    /// Set the key.
+    public void setKey( byte[] key )
+	{
+	deskey( key, true, encryptKeys );
+	deskey( key, false, decryptKeys );
+	}
+
+    // Turn an 8-byte key into internal keys.
+    private void deskey( byte[] keyBlock, boolean encrypting, int[] KnL )
+	{
+	int i, j, l, m, n;
+	int[] pc1m = new int[56];
+	int[] pcr = new int[56];
+	int[] kn = new int[32];
+
+	for ( j = 0; j < 56; ++j )
+	    {
+	    l = pc1[j];
+	    m = l & 07;
+	    pc1m[j] = ( (keyBlock[l >>> 3] & bytebit[m]) != 0 )? 1: 0;
+	    }
+
+	for ( i = 0; i < 16; ++i )
+	    {
+	    if ( encrypting )
+		m = i << 1;
+	    else
+		m = (15-i) << 1;
+	    n = m+1;
+	    kn[m] = kn[n] = 0;
+	    for ( j = 0; j < 28; ++j )
+		{
+		l = j+totrot[i];
+		if ( l < 28 )
+		    pcr[j] = pc1m[l];
+		else
+		    pcr[j] = pc1m[l-28];
+		}
+	    for ( j=28; j < 56; ++j )
+		{
+		l = j+totrot[i];
+		if ( l < 56 )
+		    pcr[j] = pc1m[l];
+		else
+		    pcr[j] = pc1m[l-28];
+		}
+	    for ( j = 0; j < 24; ++j )
+		{
+		if ( pcr[pc2[j]] != 0 )
+		    kn[m] |= bigbyte[j];
+		if ( pcr[pc2[j+24]] != 0 )
+		    kn[n] |= bigbyte[j];
+		}
+	    }
+	cookey( kn, KnL );
+	}
+
+    private void cookey( int[] raw, int KnL[] )
+	{
+	int raw0, raw1;
+	int rawi, KnLi;
+	int i;
+
+	for ( i = 0, rawi = 0, KnLi = 0; i < 16; ++i )
+	    {
+	    raw0 = raw[rawi++];
+	    raw1 = raw[rawi++];
+	    KnL[KnLi]  = (raw0 & 0x00fc0000) <<   6;
+	    KnL[KnLi] |= (raw0 & 0x00000fc0) <<  10;
+	    KnL[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+	    KnL[KnLi] |= (raw1 & 0x00000fc0) >>>  6;
+	    ++KnLi;
+	    KnL[KnLi]  = (raw0 & 0x0003f000) <<  12;
+	    KnL[KnLi] |= (raw0 & 0x0000003f) <<  16;
+	    KnL[KnLi] |= (raw1 & 0x0003f000) >>>  4;
+	    KnL[KnLi] |= (raw1 & 0x0000003f);
+	    ++KnLi;
+	    }
+	}
+
+
+    // Block encryption routines.
+
+    private int[] tempInts = new int[2];
+
+    /// Encrypt a block of eight bytes.
+    public void encrypt( byte[] clearText, int clearOff, byte[] cipherText, int cipherOff )
+	{
+	squashBytesToInts( clearText, clearOff, tempInts, 0, 2 );
+	des( tempInts, tempInts, encryptKeys );
+	spreadIntsToBytes( tempInts, 0, cipherText, cipherOff, 2 );
+	}
+
+    /// Decrypt a block of eight bytes.
+    public void decrypt( byte[] cipherText, int cipherOff, byte[] clearText, int clearOff )
+	{
+	squashBytesToInts( cipherText, cipherOff, tempInts, 0, 2 );
+	des( tempInts, tempInts, decryptKeys );
+	spreadIntsToBytes( tempInts, 0, clearText, clearOff, 2 );
+	}
+
+    // The DES function.
+    private void des( int[] inInts, int[] outInts, int[] keys )
+	{
+	int fval, work, right, leftt;
+	int round;
+	int keysi = 0;
+
+	leftt = inInts[0];
+	right = inInts[1];
+
+	work   = ((leftt >>>  4) ^ right) & 0x0f0f0f0f;
+	right ^= work;
+	leftt ^= (work << 4);
+
+	work   = ((leftt >>> 16) ^ right) & 0x0000ffff;
+	right ^= work;
+	leftt ^= (work << 16);
+
+	work   = ((right >>>  2) ^ leftt) & 0x33333333;
+	leftt ^= work;
+	right ^= (work << 2);
+
+	work   = ((right >>>  8) ^ leftt) & 0x00ff00ff;
+	leftt ^= work;
+	right ^= (work << 8);
+	right  = (right << 1) | ((right >>> 31) & 1);
+
+	work   = (leftt ^ right) & 0xaaaaaaaa;
+	leftt ^= work;
+	right ^= work;
+	leftt  = (leftt << 1) | ((leftt >>> 31) & 1);
+
+	for ( round = 0; round < 8; ++round )
+	    {
+	    work   = (right << 28) | (right >>> 4);
+	    work  ^= keys[keysi++];
+	    fval   = SP7[ work	       & 0x0000003f ];
+	    fval  |= SP5[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP3[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP1[(work >>> 24) & 0x0000003f ];
+	    work   = right ^ keys[keysi++];
+	    fval  |= SP8[ work         & 0x0000003f ];
+	    fval  |= SP6[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP4[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP2[(work >>> 24) & 0x0000003f ];
+	    leftt ^= fval;
+	    work   = (leftt << 28) | (leftt >>> 4);
+	    work  ^= keys[keysi++];
+	    fval   = SP7[ work	       & 0x0000003f ];
+	    fval  |= SP5[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP3[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP1[(work >>> 24) & 0x0000003f ];
+	    work   = leftt ^ keys[keysi++];
+	    fval  |= SP8[ work	       & 0x0000003f ];
+	    fval  |= SP6[(work >>>  8) & 0x0000003f ];
+	    fval  |= SP4[(work >>> 16) & 0x0000003f ];
+	    fval  |= SP2[(work >>> 24) & 0x0000003f ];
+	    right ^= fval;
+	    }
+
+	right  = (right << 31) | (right >>> 1);
+	work   = (leftt ^ right) & 0xaaaaaaaa;
+	leftt ^= work;
+	right ^= work;
+	leftt  = (leftt << 31) | (leftt >>> 1);
+	work   = ((leftt >>>  8) ^ right) & 0x00ff00ff;
+	right ^= work;
+	leftt ^= (work << 8);
+	work   = ((leftt >>>  2) ^ right) & 0x33333333;
+	right ^= work;
+	leftt ^= (work << 2);
+	work   = ((right >>> 16) ^ leftt) & 0x0000ffff;
+	leftt ^= work;
+	right ^= (work << 16);
+	work   = ((right >>>  4) ^ leftt) & 0x0f0f0f0f;
+	leftt ^= work;
+	right ^= (work << 4);
+	outInts[0] = right;
+	outInts[1] = leftt;
+	}
+
+
+    // Tables, permutations, S-boxes, etc.
+
+    private static byte[] bytebit = {
+	(byte)0x01, (byte)0x02, (byte)0x04, (byte)0x08,
+	(byte)0x10, (byte)0x20, (byte)0x40, (byte)0x80
+	};
+    private static int[] bigbyte = {
+	0x800000, 0x400000, 0x200000, 0x100000,
+	0x080000, 0x040000, 0x020000, 0x010000,
+	0x008000, 0x004000, 0x002000, 0x001000,
+	0x000800, 0x000400, 0x000200, 0x000100,
+	0x000080, 0x000040, 0x000020, 0x000010,
+	0x000008, 0x000004, 0x000002, 0x000001
+	};
+    private static byte[] pc1 = {
+         (byte)56, (byte)48, (byte)40, (byte)32, (byte)24, (byte)16, (byte) 8,
+      (byte) 0, (byte)57, (byte)49, (byte)41, (byte)33, (byte)25, (byte)17,
+	 (byte) 9, (byte) 1, (byte)58, (byte)50, (byte)42, (byte)34, (byte)26,
+      (byte)18, (byte)10, (byte) 2, (byte)59, (byte)51, (byte)43, (byte)35,
+	 (byte)62, (byte)54, (byte)46, (byte)38, (byte)30, (byte)22, (byte)14,
+      (byte) 6, (byte)61, (byte)53, (byte)45, (byte)37, (byte)29, (byte)21,
+	 (byte)13, (byte) 5, (byte)60, (byte)52, (byte)44, (byte)36, (byte)28,
+      (byte)20, (byte)12, (byte) 4, (byte)27, (byte)19, (byte)11, (byte)3
+	};
+    private static int[] totrot = {
+        1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28
+	};
+
+    private static byte[] pc2 = {
+	(byte)13, (byte)16, (byte)10, (byte)23, (byte) 0, (byte) 4,
+	          (byte) 2, (byte)27, (byte)14, (byte) 5, (byte)20, (byte) 9,
+	(byte)22, (byte)18, (byte)11, (byte)3 , (byte)25, (byte) 7,
+	          (byte)15, (byte) 6, (byte)26, (byte)19, (byte)12, (byte) 1,
+	(byte)40, (byte)51, (byte)30, (byte)36, (byte)46, (byte)54,
+	          (byte)29, (byte)39, (byte)50, (byte)44, (byte)32, (byte)47,
+	(byte)43, (byte)48, (byte)38, (byte)55, (byte)33, (byte)52,
+	          (byte)45, (byte)41, (byte)49, (byte)35, (byte)28, (byte)31,
+	};
+
+    private static int[] SP1 = {
+        0x01010400, 0x00000000, 0x00010000, 0x01010404,
+	0x01010004, 0x00010404, 0x00000004, 0x00010000,
+	0x00000400, 0x01010400, 0x01010404, 0x00000400,
+	0x01000404, 0x01010004, 0x01000000, 0x00000004,
+	0x00000404, 0x01000400, 0x01000400, 0x00010400,
+	0x00010400, 0x01010000, 0x01010000, 0x01000404,
+	0x00010004, 0x01000004, 0x01000004, 0x00010004,
+	0x00000000, 0x00000404, 0x00010404, 0x01000000,
+	0x00010000, 0x01010404, 0x00000004, 0x01010000,
+	0x01010400, 0x01000000, 0x01000000, 0x00000400,
+	0x01010004, 0x00010000, 0x00010400, 0x01000004,
+	0x00000400, 0x00000004, 0x01000404, 0x00010404,
+	0x01010404, 0x00010004, 0x01010000, 0x01000404,
+	0x01000004, 0x00000404, 0x00010404, 0x01010400,
+	0x00000404, 0x01000400, 0x01000400, 0x00000000,
+	0x00010004, 0x00010400, 0x00000000, 0x01010004
+	};
+    private static int[] SP2 = {
+	0x80108020, 0x80008000, 0x00008000, 0x00108020,
+	0x00100000, 0x00000020, 0x80100020, 0x80008020,
+	0x80000020, 0x80108020, 0x80108000, 0x80000000,
+	0x80008000, 0x00100000, 0x00000020, 0x80100020,
+	0x00108000, 0x00100020, 0x80008020, 0x00000000,
+	0x80000000, 0x00008000, 0x00108020, 0x80100000,
+	0x00100020, 0x80000020, 0x00000000, 0x00108000,
+	0x00008020, 0x80108000, 0x80100000, 0x00008020,
+	0x00000000, 0x00108020, 0x80100020, 0x00100000,
+	0x80008020, 0x80100000, 0x80108000, 0x00008000,
+	0x80100000, 0x80008000, 0x00000020, 0x80108020,
+	0x00108020, 0x00000020, 0x00008000, 0x80000000,
+	0x00008020, 0x80108000, 0x00100000, 0x80000020,
+	0x00100020, 0x80008020, 0x80000020, 0x00100020,
+	0x00108000, 0x00000000, 0x80008000, 0x00008020,
+	0x80000000, 0x80100020, 0x80108020, 0x00108000
+	};
+    private static int[] SP3 = {
+	0x00000208, 0x08020200, 0x00000000, 0x08020008,
+	0x08000200, 0x00000000, 0x00020208, 0x08000200,
+	0x00020008, 0x08000008, 0x08000008, 0x00020000,
+	0x08020208, 0x00020008, 0x08020000, 0x00000208,
+	0x08000000, 0x00000008, 0x08020200, 0x00000200,
+	0x00020200, 0x08020000, 0x08020008, 0x00020208,
+	0x08000208, 0x00020200, 0x00020000, 0x08000208,
+	0x00000008, 0x08020208, 0x00000200, 0x08000000,
+	0x08020200, 0x08000000, 0x00020008, 0x00000208,
+	0x00020000, 0x08020200, 0x08000200, 0x00000000,
+	0x00000200, 0x00020008, 0x08020208, 0x08000200,
+	0x08000008, 0x00000200, 0x00000000, 0x08020008,
+	0x08000208, 0x00020000, 0x08000000, 0x08020208,
+	0x00000008, 0x00020208, 0x00020200, 0x08000008,
+	0x08020000, 0x08000208, 0x00000208, 0x08020000,
+	0x00020208, 0x00000008, 0x08020008, 0x00020200
+	};
+    private static int[] SP4 = {
+	0x00802001, 0x00002081, 0x00002081, 0x00000080,
+	0x00802080, 0x00800081, 0x00800001, 0x00002001,
+	0x00000000, 0x00802000, 0x00802000, 0x00802081,
+	0x00000081, 0x00000000, 0x00800080, 0x00800001,
+	0x00000001, 0x00002000, 0x00800000, 0x00802001,
+	0x00000080, 0x00800000, 0x00002001, 0x00002080,
+	0x00800081, 0x00000001, 0x00002080, 0x00800080,
+	0x00002000, 0x00802080, 0x00802081, 0x00000081,
+	0x00800080, 0x00800001, 0x00802000, 0x00802081,
+	0x00000081, 0x00000000, 0x00000000, 0x00802000,
+	0x00002080, 0x00800080, 0x00800081, 0x00000001,
+	0x00802001, 0x00002081, 0x00002081, 0x00000080,
+	0x00802081, 0x00000081, 0x00000001, 0x00002000,
+	0x00800001, 0x00002001, 0x00802080, 0x00800081,
+	0x00002001, 0x00002080, 0x00800000, 0x00802001,
+	0x00000080, 0x00800000, 0x00002000, 0x00802080
+	};
+    private static int[] SP5 = {
+	0x00000100, 0x02080100, 0x02080000, 0x42000100,
+	0x00080000, 0x00000100, 0x40000000, 0x02080000,
+	0x40080100, 0x00080000, 0x02000100, 0x40080100,
+	0x42000100, 0x42080000, 0x00080100, 0x40000000,
+	0x02000000, 0x40080000, 0x40080000, 0x00000000,
+	0x40000100, 0x42080100, 0x42080100, 0x02000100,
+	0x42080000, 0x40000100, 0x00000000, 0x42000000,
+	0x02080100, 0x02000000, 0x42000000, 0x00080100,
+	0x00080000, 0x42000100, 0x00000100, 0x02000000,
+	0x40000000, 0x02080000, 0x42000100, 0x40080100,
+	0x02000100, 0x40000000, 0x42080000, 0x02080100,
+	0x40080100, 0x00000100, 0x02000000, 0x42080000,
+	0x42080100, 0x00080100, 0x42000000, 0x42080100,
+	0x02080000, 0x00000000, 0x40080000, 0x42000000,
+	0x00080100, 0x02000100, 0x40000100, 0x00080000,
+	0x00000000, 0x40080000, 0x02080100, 0x40000100
+	};
+    private static int[] SP6 = {
+	0x20000010, 0x20400000, 0x00004000, 0x20404010,
+	0x20400000, 0x00000010, 0x20404010, 0x00400000,
+	0x20004000, 0x00404010, 0x00400000, 0x20000010,
+	0x00400010, 0x20004000, 0x20000000, 0x00004010,
+	0x00000000, 0x00400010, 0x20004010, 0x00004000,
+	0x00404000, 0x20004010, 0x00000010, 0x20400010,
+	0x20400010, 0x00000000, 0x00404010, 0x20404000,
+	0x00004010, 0x00404000, 0x20404000, 0x20000000,
+	0x20004000, 0x00000010, 0x20400010, 0x00404000,
+	0x20404010, 0x00400000, 0x00004010, 0x20000010,
+	0x00400000, 0x20004000, 0x20000000, 0x00004010,
+	0x20000010, 0x20404010, 0x00404000, 0x20400000,
+	0x00404010, 0x20404000, 0x00000000, 0x20400010,
+	0x00000010, 0x00004000, 0x20400000, 0x00404010,
+	0x00004000, 0x00400010, 0x20004010, 0x00000000,
+	0x20404000, 0x20000000, 0x00400010, 0x20004010
+	};
+    private static int[] SP7 = {
+	0x00200000, 0x04200002, 0x04000802, 0x00000000,
+	0x00000800, 0x04000802, 0x00200802, 0x04200800,
+	0x04200802, 0x00200000, 0x00000000, 0x04000002,
+	0x00000002, 0x04000000, 0x04200002, 0x00000802,
+	0x04000800, 0x00200802, 0x00200002, 0x04000800,
+	0x04000002, 0x04200000, 0x04200800, 0x00200002,
+	0x04200000, 0x00000800, 0x00000802, 0x04200802,
+	0x00200800, 0x00000002, 0x04000000, 0x00200800,
+	0x04000000, 0x00200800, 0x00200000, 0x04000802,
+	0x04000802, 0x04200002, 0x04200002, 0x00000002,
+	0x00200002, 0x04000000, 0x04000800, 0x00200000,
+	0x04200800, 0x00000802, 0x00200802, 0x04200800,
+	0x00000802, 0x04000002, 0x04200802, 0x04200000,
+	0x00200800, 0x00000000, 0x00000002, 0x04200802,
+	0x00000000, 0x00200802, 0x04200000, 0x00000800,
+	0x04000002, 0x04000800, 0x00000800, 0x00200002
+	};
+    private static int[] SP8 = {
+	0x10001040, 0x00001000, 0x00040000, 0x10041040,
+	0x10000000, 0x10001040, 0x00000040, 0x10000000,
+	0x00040040, 0x10040000, 0x10041040, 0x00041000,
+	0x10041000, 0x00041040, 0x00001000, 0x00000040,
+	0x10040000, 0x10000040, 0x10001000, 0x00001040,
+	0x00041000, 0x00040040, 0x10040040, 0x10041000,
+	0x00001040, 0x00000000, 0x00000000, 0x10040040,
+	0x10000040, 0x10001000, 0x00041040, 0x00040000,
+	0x00041040, 0x00040000, 0x10041000, 0x00001000,
+	0x00000040, 0x10040040, 0x00001000, 0x00041040,
+	0x10001000, 0x00000040, 0x10000040, 0x10040000,
+	0x10040040, 0x10000000, 0x00040000, 0x10001040,
+	0x00000000, 0x10041040, 0x00040040, 0x10000040,
+	0x10040000, 0x10001000, 0x10001040, 0x00000000,
+	0x10041040, 0x00041000, 0x00041000, 0x00001040,
+	0x00001040, 0x00040040, 0x10000000, 0x10041000
+	};
+
+    // Routines taken from other parts of the Acme utilities.
+
+    /// Squash bytes down to ints.
+    public static void squashBytesToInts( byte[] inBytes, int inOff, int[] outInts, int outOff, int intLen )
+        {
+	for ( int i = 0; i < intLen; ++i )
+	    outInts[outOff + i] = 
+		( ( inBytes[inOff + i * 4    ] & 0xff ) << 24 ) |
+		( ( inBytes[inOff + i * 4 + 1] & 0xff ) << 16 ) |
+		( ( inBytes[inOff + i * 4 + 2] & 0xff ) <<  8 ) |
+		  ( inBytes[inOff + i * 4 + 3] & 0xff );
+        }
+
+    /// Spread ints into bytes.
+    public static void spreadIntsToBytes( int[] inInts, int inOff, byte[] outBytes, int outOff, int intLen )
+        {
+	for ( int i = 0; i < intLen; ++i )
+	    {
+	    outBytes[outOff + i * 4    ] = (byte) ( inInts[inOff + i] >>> 24 );
+	    outBytes[outOff + i * 4 + 1] = (byte) ( inInts[inOff + i] >>> 16 );
+	    outBytes[outOff + i * 4 + 2] = (byte) ( inInts[inOff + i] >>>  8 );
+	    outBytes[outOff + i * 4 + 3] = (byte)   inInts[inOff + i];
+	    }
+        }
+    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/HTTPConnectSocket.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,59 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// HTTPConnectSocket.java together with HTTPConnectSocketFactory.java
+// implement an alternate way to connect to VNC servers via one or two
+// HTTP proxies supporting the HTTP CONNECT method.
+//
+
+import java.net.*;
+import java.io.*;
+
+class HTTPConnectSocket extends Socket {
+
+  public HTTPConnectSocket(String host, int port,
+			   String proxyHost, int proxyPort)
+    throws IOException {
+
+    // Connect to the specified HTTP proxy
+    super(proxyHost, proxyPort);
+
+    // Send the CONNECT request
+    getOutputStream().write(("CONNECT " + host + ":" + port +
+			     " HTTP/1.0\r\n\r\n").getBytes());
+
+    // Read the first line of the response
+    DataInputStream is = new DataInputStream(getInputStream());
+    String str = is.readLine();
+
+    // Check the HTTP error code -- it should be "200" on success
+    if (!str.startsWith("HTTP/1.0 200 ")) {
+      if (str.startsWith("HTTP/1.0 "))
+	str = str.substring(9);
+      throw new IOException("Proxy reports \"" + str + "\"");
+    }
+
+    // Success -- skip remaining HTTP headers
+    do {
+      str = is.readLine();
+    } while (str.length() != 0);
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/HTTPConnectSocketFactory.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,86 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky, Inc.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// HTTPConnectSocketFactory.java together with HTTPConnectSocket.java
+// implement an alternate way to connect to VNC servers via one or two
+// HTTP proxies supporting the HTTP CONNECT method.
+//
+
+import java.applet.*;
+import java.net.*;
+import java.io.*;
+
+class HTTPConnectSocketFactory implements SocketFactory {
+
+  public Socket createSocket(String host, int port, Applet applet)
+    throws IOException {
+
+    return createSocket(host, port,
+			applet.getParameter("PROXYHOST1"),
+			applet.getParameter("PROXYPORT1"));
+  }
+
+  public Socket createSocket(String host, int port, String[] args)
+    throws IOException {
+
+    return createSocket(host, port,
+			readArg(args, "PROXYHOST1"),
+			readArg(args, "PROXYPORT1"));
+  }
+
+  public Socket createSocket(String host, int port,
+			     String proxyHost, String proxyPortStr)
+    throws IOException {
+
+    int proxyPort = 0;
+    if (proxyPortStr != null) {
+      try {
+	proxyPort = Integer.parseInt(proxyPortStr);
+      } catch (NumberFormatException e) { }
+    }
+
+    if (proxyHost == null || proxyPort == 0) {
+      System.out.println("Incomplete parameter list for HTTPConnectSocket");
+      return new Socket(host, port);
+    }
+
+    System.out.println("HTTP CONNECT via proxy " + proxyHost +
+		       " port " + proxyPort);
+    HTTPConnectSocket s =
+      new HTTPConnectSocket(host, port, proxyHost, proxyPort);
+
+    return (Socket)s;
+  }
+
+  private String readArg(String[] args, String name) {
+
+    for (int i = 0; i < args.length; i += 2) {
+      if (args[i].equalsIgnoreCase(name)) {
+	try {
+	  return args[i+1];
+	} catch (Exception e) {
+	  return null;
+	}
+      }
+    }
+    return null;
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/InStream.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,177 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// rdr::InStream marshalls data from a buffer stored in RDR (RFB Data
+// Representation).
+//
+
+abstract public class InStream {
+
+  // check() ensures there is buffer data for at least one item of size
+  // itemSize bytes.  Returns the number of items in the buffer (up to a
+  // maximum of nItems).
+
+  public final int check(int itemSize, int nItems) throws Exception {
+    if (ptr + itemSize * nItems > end) {
+      if (ptr + itemSize > end)
+        return overrun(itemSize, nItems);
+
+      nItems = (end - ptr) / itemSize;
+    }
+    return nItems;
+  }
+
+  public final void check(int itemSize) throws Exception {
+    if (ptr + itemSize > end)
+      overrun(itemSize, 1);
+  }
+
+  // readU/SN() methods read unsigned and signed N-bit integers.
+
+  public final int readS8() throws Exception {
+    check(1); return b[ptr++];
+  }
+
+  public final int readS16() throws Exception {
+    check(2); int b0 = b[ptr++];
+    int b1 = b[ptr++] & 0xff; return b0 << 8 | b1;
+  }
+
+  public final int readS32() throws Exception {
+    check(4); int b0 = b[ptr++];
+    int b1 = b[ptr++] & 0xff;
+    int b2 = b[ptr++] & 0xff;
+    int b3 = b[ptr++] & 0xff;
+    return b0 << 24 | b1 << 16 | b2 << 8 | b3;
+  }
+
+  public final int readU8() throws Exception {
+    return readS8() & 0xff;
+  }
+
+  public final int readU16() throws Exception {
+    return readS16() & 0xffff;
+  }
+
+  public final int readU32() throws Exception {
+    return readS32() & 0xffffffff;
+  }
+
+  // readString() reads a string - a U32 length followed by the data.
+
+  public final String readString() throws Exception {
+    int len = readU32();
+    if (len > maxStringLength)
+      throw new Exception("InStream max string length exceeded");
+
+    char[] str = new char[len];
+    int i = 0;
+    while (i < len) {
+      int j = i + check(1, len - i);
+      while (i < j) {
+	str[i++] = (char)b[ptr++];
+      }
+    }
+
+    return new String(str);
+  }
+
+  // maxStringLength protects against allocating a huge buffer.  Set it
+  // higher if you need longer strings.
+
+  public static int maxStringLength = 65535;
+
+  public final void skip(int bytes) throws Exception {
+    while (bytes > 0) {
+      int n = check(1, bytes);
+      ptr += n;
+      bytes -= n;
+    }
+  }
+
+  // readBytes() reads an exact number of bytes into an array at an offset.
+
+  public void readBytes(byte[] data, int offset, int length) throws Exception {
+    int offsetEnd = offset + length;
+    while (offset < offsetEnd) {
+      int n = check(1, offsetEnd - offset);
+      System.arraycopy(b, ptr, data, offset, n);
+      ptr += n;
+      offset += n;
+    }
+  }
+
+  // readOpaqueN() reads a quantity "without byte-swapping".  Because java has
+  // no byte-ordering, we just use big-endian.
+
+  public final int readOpaque8() throws Exception {
+    return readU8();
+  }
+
+  public final int readOpaque16() throws Exception {
+    return readU16();
+  }
+
+  public final int readOpaque32() throws Exception {
+    return readU32();
+  }
+
+  public final int readOpaque24A() throws Exception {
+    check(3); int b0 = b[ptr++];
+    int b1 = b[ptr++]; int b2 = b[ptr++];
+    return b0 << 24 | b1 << 16 | b2 << 8;
+  }
+
+  public final int readOpaque24B() throws Exception {
+    check(3); int b0 = b[ptr++];
+    int b1 = b[ptr++]; int b2 = b[ptr++];
+    return b0 << 16 | b1 << 8 | b2;
+  }
+
+  // pos() returns the position in the stream.
+
+  abstract public int pos();
+
+  // bytesAvailable() returns true if at least one byte can be read from the
+  // stream without blocking.  i.e. if false is returned then readU8() would
+  // block.
+
+  public boolean bytesAvailable() { return end != ptr; }
+
+  // getbuf(), getptr(), getend() and setptr() are "dirty" methods which allow
+  // you to manipulate the buffer directly.  This is useful for a stream which
+  // is a wrapper around an underlying stream.
+
+  public final byte[] getbuf() { return b; }
+  public final int getptr() { return ptr; }
+  public final int getend() { return end; }
+  public final void setptr(int p) { ptr = p; }
+
+  // overrun() is implemented by a derived class to cope with buffer overrun.
+  // It ensures there are at least itemSize bytes of buffer data.  Returns
+  // the number of items in the buffer (up to a maximum of nItems).  itemSize
+  // is supposed to be "small" (a few bytes).
+
+  abstract protected int overrun(int itemSize, int nItems) throws Exception;
+
+  protected InStream() {}
+  protected byte[] b;
+  protected int ptr;
+  protected int end;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/MemInStream.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,32 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+public class MemInStream extends InStream {
+
+  public MemInStream(byte[] data, int offset, int len) {
+    b = data;
+    ptr = offset;
+    end = offset + len;
+  }
+
+  public int pos() { return ptr; }
+
+  protected int overrun(int itemSize, int nItems) throws Exception {
+    throw new Exception("MemInStream overrun: end of stream");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/OptionsFrame.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,405 @@
+//
+//  Copyright (C) 2001 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2001 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// Options frame.
+//
+// This deals with all the options the user can play with.
+// It sets the encodings array and some booleans.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+
+class OptionsFrame extends Frame
+  implements WindowListener, ActionListener, ItemListener {
+
+  static String[] names = {
+    "Encoding",
+    "Compression level",
+    "JPEG image quality",
+    "Cursor shape updates",
+    "Use CopyRect",
+    "Restricted colors",
+    "Mouse buttons 2 and 3",
+    "View only",
+    "Scale remote cursor",
+    "Share desktop",
+  };
+
+  static String[][] values = {
+    { "Auto", "Raw", "RRE", "CoRRE", "Hextile", "Zlib", "Tight", "ZRLE" },
+    { "Default", "1", "2", "3", "4", "5", "6", "7", "8", "9" },
+    { "JPEG off", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" },
+    { "Enable", "Ignore", "Disable" },
+    { "Yes", "No" },
+    { "Yes", "No" },
+    { "Normal", "Reversed" },
+    { "Yes", "No" },
+    { "No", "50%", "75%", "125%", "150%" },
+    { "Yes", "No" },
+  };
+
+  final int
+    encodingIndex        = 0,
+    compressLevelIndex   = 1,
+    jpegQualityIndex     = 2,
+    cursorUpdatesIndex   = 3,
+    useCopyRectIndex     = 4,
+    eightBitColorsIndex  = 5,
+    mouseButtonIndex     = 6,
+    viewOnlyIndex        = 7,
+    scaleCursorIndex     = 8,
+    shareDesktopIndex    = 9;
+
+  Label[] labels = new Label[names.length];
+  Choice[] choices = new Choice[names.length];
+  Button closeButton;
+  VncViewer viewer;
+
+
+  //
+  // The actual data which other classes look at:
+  //
+
+  int preferredEncoding;
+  int compressLevel;
+  int jpegQuality;
+  boolean useCopyRect;
+  boolean requestCursorUpdates;
+  boolean ignoreCursorUpdates;
+
+  boolean eightBitColors;
+
+  boolean reverseMouseButtons2And3;
+  boolean shareDesktop;
+  boolean viewOnly;
+  int scaleCursor;
+
+  boolean autoScale;
+  int scalingFactor;
+
+  //
+  // Constructor.  Set up the labels and choices from the names and values
+  // arrays.
+  //
+
+  OptionsFrame(VncViewer v) {
+    super("TightVNC Options");
+
+    viewer = v;
+
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.fill = GridBagConstraints.BOTH;
+
+    for (int i = 0; i < names.length; i++) {
+      labels[i] = new Label(names[i]);
+      gbc.gridwidth = 1;
+      gridbag.setConstraints(labels[i],gbc);
+      add(labels[i]);
+
+      choices[i] = new Choice();
+      gbc.gridwidth = GridBagConstraints.REMAINDER;
+      gridbag.setConstraints(choices[i],gbc);
+      add(choices[i]);
+      choices[i].addItemListener(this);
+
+      for (int j = 0; j < values[i].length; j++) {
+	choices[i].addItem(values[i][j]);
+      }
+    }
+
+    closeButton = new Button("Close");
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gridbag.setConstraints(closeButton, gbc);
+    add(closeButton);
+    closeButton.addActionListener(this);
+
+    pack();
+
+    addWindowListener(this);
+
+    // Set up defaults
+
+    choices[encodingIndex].select("Auto");
+    choices[compressLevelIndex].select("Default");
+    choices[jpegQualityIndex].select("6");
+    choices[cursorUpdatesIndex].select("Enable");
+    choices[useCopyRectIndex].select("Yes");
+    choices[eightBitColorsIndex].select("No");
+    choices[mouseButtonIndex].select("Normal");
+    choices[viewOnlyIndex].select("No");
+    choices[scaleCursorIndex].select("No");
+    choices[shareDesktopIndex].select("Yes");
+
+    // But let them be overridden by parameters
+
+    for (int i = 0; i < names.length; i++) {
+      String s = viewer.readParameter(names[i], false);
+      if (s != null) {
+	for (int j = 0; j < values[i].length; j++) {
+	  if (s.equalsIgnoreCase(values[i][j])) {
+	    choices[i].select(j);
+	  }
+	}
+      }
+    }
+
+    // FIXME: Provide some sort of GUI for "Scaling Factor".
+
+    autoScale = false;
+    scalingFactor = 100;
+    String s = viewer.readParameter("Scaling Factor", false);
+    if (s != null) {
+      if (s.equalsIgnoreCase("Auto")) {
+	autoScale = true;
+      } else {
+	// Remove the '%' char at the end of string if present.
+	if (s.charAt(s.length() - 1) == '%') {
+	  s = s.substring(0, s.length() - 1);
+	}
+	// Convert to an integer.
+	try {
+	  scalingFactor = Integer.parseInt(s);
+	}
+	catch (NumberFormatException e) {
+	  scalingFactor = 100;
+	}
+	// Make sure scalingFactor is in the range of [1..1000].
+	if (scalingFactor < 1) {
+	  scalingFactor = 1;
+	} else if (scalingFactor > 1000) {
+	  scalingFactor = 1000;
+	}
+      }
+    }
+
+    // Make the booleans and encodings array correspond to the state of the GUI
+
+    setEncodings();
+    setColorFormat();
+    setOtherOptions();
+  }
+
+
+  //
+  // Disable the shareDesktop option
+  //
+
+  void disableShareDesktop() {
+    labels[shareDesktopIndex].setEnabled(false);
+    choices[shareDesktopIndex].setEnabled(false);
+  }
+
+  //
+  // setEncodings looks at the encoding, compression level, JPEG
+  // quality level, cursor shape updates and copyRect choices and sets
+  // corresponding variables properly. Then it calls the VncViewer's
+  // setEncodings method to send a SetEncodings message to the RFB
+  // server.
+  //
+
+  void setEncodings() {
+    useCopyRect = choices[useCopyRectIndex].getSelectedItem().equals("Yes");
+
+    preferredEncoding = RfbProto.EncodingRaw;
+    boolean enableCompressLevel = false;
+
+    if (choices[encodingIndex].getSelectedItem().equals("RRE")) {
+      preferredEncoding = RfbProto.EncodingRRE;
+    } else if (choices[encodingIndex].getSelectedItem().equals("CoRRE")) {
+      preferredEncoding = RfbProto.EncodingCoRRE;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Hextile")) {
+      preferredEncoding = RfbProto.EncodingHextile;
+    } else if (choices[encodingIndex].getSelectedItem().equals("ZRLE")) {
+      preferredEncoding = RfbProto.EncodingZRLE;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Zlib")) {
+      preferredEncoding = RfbProto.EncodingZlib;
+      enableCompressLevel = true;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Tight")) {
+      preferredEncoding = RfbProto.EncodingTight;
+      enableCompressLevel = true;
+    } else if (choices[encodingIndex].getSelectedItem().equals("Auto")) {
+      preferredEncoding = -1;
+    }
+
+    // Handle compression level setting.
+
+    try {
+      compressLevel =
+        Integer.parseInt(choices[compressLevelIndex].getSelectedItem());
+    }
+    catch (NumberFormatException e) {
+      compressLevel = -1;
+    }
+    if (compressLevel < 1 || compressLevel > 9) {
+      compressLevel = -1;
+    }
+    labels[compressLevelIndex].setEnabled(enableCompressLevel);
+    choices[compressLevelIndex].setEnabled(enableCompressLevel);
+
+    // Handle JPEG quality setting.
+
+    try {
+      jpegQuality =
+        Integer.parseInt(choices[jpegQualityIndex].getSelectedItem());
+    }
+    catch (NumberFormatException e) {
+      jpegQuality = -1;
+    }
+    if (jpegQuality < 0 || jpegQuality > 9) {
+      jpegQuality = -1;
+    }
+
+    // Request cursor shape updates if necessary.
+
+    requestCursorUpdates =
+      !choices[cursorUpdatesIndex].getSelectedItem().equals("Disable");
+
+    if (requestCursorUpdates) {
+      ignoreCursorUpdates =
+	choices[cursorUpdatesIndex].getSelectedItem().equals("Ignore");
+    }
+
+    viewer.setEncodings();
+  }
+
+  //
+  // setColorFormat sets eightBitColors variable depending on the GUI
+  // setting, causing switches between 8-bit and 24-bit colors mode if
+  // necessary.
+  //
+
+  void setColorFormat() {
+
+    eightBitColors =
+      choices[eightBitColorsIndex].getSelectedItem().equals("Yes");
+
+    boolean enableJPEG = !eightBitColors;
+
+    labels[jpegQualityIndex].setEnabled(enableJPEG);
+    choices[jpegQualityIndex].setEnabled(enableJPEG);
+  }
+
+  //
+  // setOtherOptions looks at the "other" choices (ones that do not
+  // cause sending any protocol messages) and sets the boolean flags
+  // appropriately.
+  //
+
+  void setOtherOptions() {
+
+    reverseMouseButtons2And3
+      = choices[mouseButtonIndex].getSelectedItem().equals("Reversed");
+
+    viewOnly 
+      = choices[viewOnlyIndex].getSelectedItem().equals("Yes");
+    if (viewer.vc != null)
+      viewer.vc.enableInput(!viewOnly);
+
+    shareDesktop
+      = choices[shareDesktopIndex].getSelectedItem().equals("Yes");
+
+    String scaleString = choices[scaleCursorIndex].getSelectedItem();
+    if (scaleString.endsWith("%"))
+      scaleString = scaleString.substring(0, scaleString.length() - 1);
+    try {
+      scaleCursor = Integer.parseInt(scaleString);
+    }
+    catch (NumberFormatException e) {
+      scaleCursor = 0;
+    }
+    if (scaleCursor < 10 || scaleCursor > 500) {
+      scaleCursor = 0;
+    }
+    if (requestCursorUpdates && !ignoreCursorUpdates && !viewOnly) {
+      labels[scaleCursorIndex].setEnabled(true);
+      choices[scaleCursorIndex].setEnabled(true);
+    } else {
+      labels[scaleCursorIndex].setEnabled(false);
+      choices[scaleCursorIndex].setEnabled(false);
+    }
+    if (viewer.vc != null)
+      viewer.vc.createSoftCursor(); // update cursor scaling
+  }
+
+
+  //
+  // Respond to actions on Choice controls
+  //
+
+  public void itemStateChanged(ItemEvent evt) {
+    Object source = evt.getSource();
+
+    if (source == choices[encodingIndex] ||
+        source == choices[compressLevelIndex] ||
+        source == choices[jpegQualityIndex] ||
+        source == choices[cursorUpdatesIndex] ||
+        source == choices[useCopyRectIndex]) {
+
+      setEncodings();
+
+      if (source == choices[cursorUpdatesIndex]) {
+        setOtherOptions();      // update scaleCursor state
+      }
+
+    } else if (source == choices[eightBitColorsIndex]) {
+
+      setColorFormat();
+
+    } else if (source == choices[mouseButtonIndex] ||
+	       source == choices[shareDesktopIndex] ||
+	       source == choices[viewOnlyIndex] ||
+	       source == choices[scaleCursorIndex]) {
+
+      setOtherOptions();
+
+    }
+  }
+
+  //
+  // Respond to button press
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+    if (evt.getSource() == closeButton)
+      setVisible(false);
+  }
+
+  //
+  // Respond to window events
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    setVisible(false);
+  }
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowDeactivated(WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/RecordingFrame.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,311 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// Recording frame. It allows to control recording RFB sessions into
+// FBS (FrameBuffer Stream) files.
+//
+
+import java.io.*;
+import java.awt.*;
+import java.awt.event.*;
+
+class RecordingFrame extends Frame
+  implements WindowListener, ActionListener {
+
+  boolean recording;
+
+  TextField fnameField;
+  Button browseButton;
+
+  Label statusLabel;
+
+  Button recordButton, nextButton, closeButton;
+  VncViewer viewer;
+
+  //
+  // Check if current security manager allows to create a
+  // RecordingFrame object.
+  //
+
+  public static boolean checkSecurity() {
+    SecurityManager security = System.getSecurityManager();
+    if (security != null) {
+      try {
+	security.checkPropertyAccess("user.dir");
+	security.checkPropertyAccess("file.separator");
+	// Work around (rare) checkPropertyAccess bug
+	System.getProperty("user.dir");
+      } catch (SecurityException e) {
+	System.out.println("SecurityManager restricts session recording.");
+	return false;
+      }
+    }
+    return true;
+  }
+
+  //
+  // Constructor.
+  //
+
+  RecordingFrame(VncViewer v) {
+    super("TightVNC Session Recording");
+
+    viewer = v;
+
+    // Determine initial filename for next saved session.
+    // FIXME: Check SecurityManager.
+
+    String fname = nextNewFilename(System.getProperty("user.dir") +
+				   System.getProperty("file.separator") +
+				   "vncsession.fbs");
+
+    // Construct new panel with file name field and "Browse" button.
+
+    Panel fnamePanel = new Panel();
+    GridBagLayout fnameGridbag = new GridBagLayout();
+    fnamePanel.setLayout(fnameGridbag);
+
+    GridBagConstraints fnameConstraints = new GridBagConstraints();
+    fnameConstraints.gridwidth = GridBagConstraints.RELATIVE;
+    fnameConstraints.fill = GridBagConstraints.BOTH;
+    fnameConstraints.weightx = 4.0;
+
+    fnameField = new TextField(fname, 64);
+    fnameGridbag.setConstraints(fnameField, fnameConstraints);
+    fnamePanel.add(fnameField);
+    fnameField.addActionListener(this);
+
+    fnameConstraints.gridwidth = GridBagConstraints.REMAINDER;
+    fnameConstraints.weightx = 1.0;
+
+    browseButton = new Button("Browse");
+    fnameGridbag.setConstraints(browseButton, fnameConstraints);
+    fnamePanel.add(browseButton);
+    browseButton.addActionListener(this);
+
+    // Construct the frame.
+
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weighty = 1.0;
+    gbc.insets = new Insets(10, 0, 0, 0);
+
+    Label helpLabel =
+      new Label("File name to save next recorded session in:", Label.CENTER);
+    gridbag.setConstraints(helpLabel, gbc);
+    add(helpLabel);
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weighty = 0.0;
+    gbc.insets = new Insets(0, 0, 0, 0);
+
+    gridbag.setConstraints(fnamePanel, gbc);
+    add(fnamePanel);
+
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weighty = 1.0;
+    gbc.insets = new Insets(10, 0, 10, 0);
+
+    statusLabel = new Label("", Label.CENTER);
+    gridbag.setConstraints(statusLabel, gbc);
+    add(statusLabel);
+
+    gbc.fill = GridBagConstraints.HORIZONTAL;
+    gbc.weightx = 1.0;
+    gbc.weighty = 0.0;
+    gbc.gridwidth = 1;
+    gbc.insets = new Insets(0, 0, 0, 0);
+
+    recordButton = new Button("Record");
+    gridbag.setConstraints(recordButton, gbc);
+    add(recordButton);
+    recordButton.addActionListener(this);
+
+    nextButton = new Button("Next file");
+    gridbag.setConstraints(nextButton, gbc);
+    add(nextButton);
+    nextButton.addActionListener(this);
+
+    closeButton = new Button("Close");
+    gridbag.setConstraints(closeButton, gbc);
+    add(closeButton);
+    closeButton.addActionListener(this);
+
+    // Set correct text, font and color for the statusLabel.
+    stopRecording();
+
+    pack();
+
+    addWindowListener(this);
+  }
+
+  //
+  // If the given string ends with ".NNN" where NNN is a decimal
+  // number, increase this number by one. Otherwise, append ".001"
+  // to the given string.
+  //
+
+  protected String nextFilename(String fname) {
+    int len = fname.length();
+    int suffixPos = len;
+    int suffixNum = 1;
+
+    if (len > 4 && fname.charAt(len - 4) == '.') {
+      try {
+	suffixNum = Integer.parseInt(fname.substring(len - 3, len)) + 1;
+	suffixPos = len - 4;
+      } catch (NumberFormatException e) { }
+    }
+
+    char[] zeroes = {'0', '0', '0'};
+    String suffix = String.valueOf(suffixNum);
+    if (suffix.length() < 3) {
+      suffix = new String(zeroes, 0, 3 - suffix.length()) + suffix;
+    }
+
+    return fname.substring(0, suffixPos) + '.' + suffix;
+  }
+
+  //
+  // Find next name of a file which does not exist yet.
+  //
+
+  protected String nextNewFilename(String fname) {
+    String newName = fname;
+    File f;
+    try {
+      do {
+	newName = nextFilename(newName);
+	f = new File(newName);
+      } while (f.exists());
+    } catch (SecurityException e) { }
+
+    return newName;
+  }
+
+  //
+  // Let the user choose a file name showing a FileDialog.
+  //
+
+  protected boolean browseFile() {
+    File currentFile = new File(fnameField.getText());
+
+    FileDialog fd =
+      new FileDialog(this, "Save next session as...", FileDialog.SAVE);
+    fd.setDirectory(currentFile.getParent());
+    fd.setVisible(true);
+    if (fd.getFile() != null) {
+      String newDir = fd.getDirectory();
+      String sep = System.getProperty("file.separator");
+      if (newDir.length() > 0) {
+	if (!sep.equals(newDir.substring(newDir.length() - sep.length())))
+	  newDir += sep;
+      }
+      String newFname = newDir + fd.getFile();
+      if (newFname.equals(fnameField.getText())) {
+	fnameField.setText(newFname);
+	return true;
+      }
+    }
+    return false;
+  }
+
+  //
+  // Start recording.
+  //
+
+  public void startRecording() {
+    statusLabel.setText("Status: Recording...");
+    statusLabel.setFont(new Font("Helvetica", Font.BOLD, 12));
+    statusLabel.setForeground(Color.red);
+    recordButton.setLabel("Stop recording");
+
+    recording = true;
+
+    viewer.setRecordingStatus(fnameField.getText());
+  }
+
+  //
+  // Stop recording.
+  //
+
+  public void stopRecording() {
+    statusLabel.setText("Status: Not recording.");
+    statusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+    statusLabel.setForeground(Color.black);
+    recordButton.setLabel("Record");
+
+    recording = false;
+
+    viewer.setRecordingStatus(null);
+  }
+
+  //
+  // Close our window properly.
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    setVisible(false);
+  }
+
+  //
+  // Ignore window events we're not interested in.
+  //
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowDeactivated (WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+
+
+  //
+  // Respond to button presses
+  //
+
+  public void actionPerformed(ActionEvent evt) {
+    if (evt.getSource() == browseButton) {
+      if (browseFile() && recording)
+	startRecording();
+
+    } else if (evt.getSource() == recordButton) {
+      if (!recording) {
+	startRecording();
+      } else {
+	stopRecording();
+        fnameField.setText(nextNewFilename(fnameField.getText()));
+      }
+
+    } else if (evt.getSource() == nextButton) {
+      fnameField.setText(nextNewFilename(fnameField.getText()));
+      if (recording)
+	startRecording();
+
+    } else if (evt.getSource() == closeButton) {
+      setVisible(false);
+
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ReloginPanel.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,65 @@
+//
+//  Copyright (C) 2002 Cendio Systems.  All Rights Reserved.
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// ReloginPanel class implements panel with a button for logging in again,
+// after fatal errors or disconnect
+//
+
+
+import java.awt.*;
+import java.awt.event.*;
+import java.applet.*;
+
+//
+// The panel which implements the Relogin button
+//
+
+class ReloginPanel extends Panel implements ActionListener {
+  Button reloginButton;
+  Button closeButton;
+  VncViewer viewer;
+
+  //
+  // Constructor.
+  //
+  public ReloginPanel(VncViewer v) {
+    viewer = v;
+    setLayout(new FlowLayout(FlowLayout.CENTER));
+    reloginButton = new Button("Login again");
+    add(reloginButton);
+    reloginButton.addActionListener(this);
+    if (viewer.inSeparateFrame) {
+      closeButton = new Button("Close window");
+      add(closeButton);
+      closeButton.addActionListener(this);
+    }
+  }
+
+  //
+  // This method is called when a button is pressed.
+  //
+  public synchronized void actionPerformed(ActionEvent evt) {
+    if (viewer.inSeparateFrame)
+      viewer.vncFrame.dispose();
+    if (evt.getSource() == reloginButton)
+      viewer.getAppletContext().showDocument(viewer.getDocumentBase());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/RfbProto.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,1398 @@
+//
+//  Copyright (C) 2001-2004 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2001-2006 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// RfbProto.java
+//
+
+import java.io.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.net.Socket;
+import java.util.zip.*;
+import java.nio.*;
+
+class RfbProto {
+
+  final static String
+    versionMsg_3_3 = "RFB 003.003\n",
+    versionMsg_3_7 = "RFB 003.007\n",
+    versionMsg_3_8 = "RFB 003.008\n";
+
+  // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC
+  final static String
+    StandardVendor  = "STDV",
+    TridiaVncVendor = "TRDV",
+    TightVncVendor  = "TGHT";
+
+  // Security types
+  final static int
+    SecTypeInvalid = 0,
+    SecTypeNone    = 1,
+    SecTypeVncAuth = 2,
+    SecTypeTight   = 16;
+
+  // Supported tunneling types
+  final static int
+    NoTunneling = 0;
+  final static String
+    SigNoTunneling = "NOTUNNEL";
+
+  // Supported authentication types
+  final static int
+    AuthNone      = 1,
+    AuthVNC       = 2,
+    AuthUnixLogin = 129;
+  final static String
+    SigAuthNone      = "NOAUTH__",
+    SigAuthVNC       = "VNCAUTH_",
+    SigAuthUnixLogin = "ULGNAUTH";
+
+  // VNC authentication results
+  final static int
+    VncAuthOK      = 0,
+    VncAuthFailed  = 1,
+    VncAuthTooMany = 2;
+
+  // Standard server-to-client messages
+  final static int
+    FramebufferUpdate   = 0,
+    SetColourMapEntries = 1,
+    Bell                = 2,
+    ServerCutText       = 3;
+
+  // Non-standard server-to-client messages
+  final static int
+    EndOfContinuousUpdates = 150;
+  final static String
+    SigEndOfContinuousUpdates = "CUS_EOCU";
+
+  // Standard client-to-server messages
+  final static int
+    SetPixelFormat           = 0,
+    FixColourMapEntries      = 1,
+    SetEncodings             = 2,
+    FramebufferUpdateRequest = 3,
+    KeyboardEvent            = 4,
+    PointerEvent             = 5,
+    ClientCutText            = 6;
+
+  // Non-standard client-to-server messages
+  final static int
+    EnableContinuousUpdates = 150;
+  final static String
+    SigEnableContinuousUpdates = "CUC_ENCU";
+
+  // Supported encodings and pseudo-encodings
+  final static int
+    EncodingRaw            = 0,
+    EncodingCopyRect       = 1,
+    EncodingRRE            = 2,
+    EncodingCoRRE          = 4,
+    EncodingHextile        = 5,
+    EncodingZlib           = 6,
+    EncodingTight          = 7,
+    EncodingZRLE           = 16,
+    EncodingCompressLevel0 = 0xFFFFFF00,
+    EncodingQualityLevel0  = 0xFFFFFFE0,
+    EncodingXCursor        = 0xFFFFFF10,
+    EncodingRichCursor     = 0xFFFFFF11,
+    EncodingPointerPos     = 0xFFFFFF18,
+    EncodingLastRect       = 0xFFFFFF20,
+    EncodingNewFBSize      = 0xFFFFFF21;
+  final static String
+    SigEncodingRaw            = "RAW_____",
+    SigEncodingCopyRect       = "COPYRECT",
+    SigEncodingRRE            = "RRE_____",
+    SigEncodingCoRRE          = "CORRE___",
+    SigEncodingHextile        = "HEXTILE_",
+    SigEncodingZlib           = "ZLIB____",
+    SigEncodingTight          = "TIGHT___",
+    SigEncodingZRLE           = "ZRLE____",
+    SigEncodingCompressLevel0 = "COMPRLVL",
+    SigEncodingQualityLevel0  = "JPEGQLVL",
+    SigEncodingXCursor        = "X11CURSR",
+    SigEncodingRichCursor     = "RCHCURSR",
+    SigEncodingPointerPos     = "POINTPOS",
+    SigEncodingLastRect       = "LASTRECT",
+    SigEncodingNewFBSize      = "NEWFBSIZ";
+
+  final static int MaxNormalEncoding = 255;
+
+  // Contstants used in the Hextile decoder
+  final static int
+    HextileRaw                 = 1,
+    HextileBackgroundSpecified = 2,
+    HextileForegroundSpecified = 4,
+    HextileAnySubrects         = 8,
+    HextileSubrectsColoured    = 16;
+
+  // Contstants used in the Tight decoder
+  final static int TightMinToCompress = 12;
+  final static int
+    TightExplicitFilter = 0x04,
+    TightFill           = 0x08,
+    TightJpeg           = 0x09,
+    TightMaxSubencoding = 0x09,
+    TightFilterCopy     = 0x00,
+    TightFilterPalette  = 0x01,
+    TightFilterGradient = 0x02;
+
+
+  String host;
+  int port;
+  Socket sock;
+  OutputStream os;
+  SessionRecorder rec;
+  boolean inNormalProtocol = false;
+  VncViewer viewer;
+
+  // Input stream is declared private to make sure it can be accessed
+  // only via RfbProto methods. We have to do this because we want to
+  // count how many bytes were read.
+  private DataInputStream is;
+  private long numBytesRead = 0;
+  public long getNumBytesRead() { return numBytesRead; }
+  
+  boolean TEST=true;
+  Socket cliSock;
+  ByteBuffer buffer;
+  int rnBytes;
+  
+
+  // Java on UNIX does not call keyPressed() on some keys, for example
+  // swedish keys To prevent our workaround to produce duplicate
+  // keypresses on JVMs that actually works, keep track of if
+  // keyPressed() for a "broken" key was called or not. 
+  boolean brokenKeyPressed = false;
+
+  // This will be set to true on the first framebuffer update
+  // containing Zlib-, ZRLE- or Tight-encoded data.
+  boolean wereZlibUpdates = false;
+
+  // This will be set to false if the startSession() was called after
+  // we have received at least one Zlib-, ZRLE- or Tight-encoded
+  // framebuffer update.
+  boolean recordFromBeginning = true;
+
+  // This fields are needed to show warnings about inefficiently saved
+  // sessions only once per each saved session file.
+  boolean zlibWarningShown;
+  boolean tightWarningShown;
+
+  // Before starting to record each saved session, we set this field
+  // to 0, and increment on each framebuffer update. We don't flush
+  // the SessionRecorder data into the file before the second update. 
+  // This allows us to write initial framebuffer update with zero
+  // timestamp, to let the player show initial desktop before
+  // playback.
+  int numUpdatesInSession;
+
+  // Measuring network throughput.
+  boolean timing;
+  long timeWaitedIn100us;
+  long timedKbits;
+
+  // Protocol version and TightVNC-specific protocol options.
+  int serverMajor, serverMinor;
+  int clientMajor, clientMinor;
+  boolean protocolTightVNC;
+  CapsContainer tunnelCaps, authCaps;
+  CapsContainer serverMsgCaps, clientMsgCaps;
+  CapsContainer encodingCaps;
+
+  // If true, informs that the RFB socket was closed.
+  private boolean closed;
+
+  //
+  // Constructor. Make TCP connection to RFB server.
+  //
+
+  RfbProto(String h, int p, VncViewer v) throws IOException {
+    viewer = v;
+    host = h;
+    port = p;
+
+    if (viewer.socketFactory == null) {
+      sock = new Socket(host, port);
+      if(TEST){
+//      	cliSock = new Socket("localhost",5550);
+    	cliSock = new Socket("dimolto.cr.ie.u-ryukyu.ac.jp",5550);
+      }
+      
+    } else {
+      try {
+	Class factoryClass = Class.forName(viewer.socketFactory);
+	SocketFactory factory = (SocketFactory)factoryClass.newInstance();
+	if (viewer.inAnApplet)
+	  sock = factory.createSocket(host, port, viewer);
+	else
+	  sock = factory.createSocket(host, port, viewer.mainArgs);
+      } catch(Exception e) {
+	e.printStackTrace();
+	throw new IOException(e.getMessage());
+      }
+    }
+    is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
+						     16384));
+    os = sock.getOutputStream();
+
+    timing = false;
+    timeWaitedIn100us = 5;
+    timedKbits = 0;
+  }
+
+
+  synchronized void close() {
+    try {
+      sock.close();
+      closed = true;
+      System.out.println("RFB socket closed");
+      if (rec != null) {
+	rec.close();
+	rec = null;
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  synchronized boolean closed() {
+    return closed;
+  }
+
+  //
+  // Read server's protocol version message
+  //
+
+  void readVersionMsg() throws Exception {
+
+    byte[] b = new byte[12];
+
+    readFully(b);
+
+    if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ')
+	|| (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9')
+	|| (b[6] < '0') || (b[6] > '9') || (b[7] != '.')
+	|| (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9')
+	|| (b[10] < '0') || (b[10] > '9') || (b[11] != '\n'))
+    {
+      throw new Exception("Host " + host + " port " + port +
+			  " is not an RFB server");
+    }
+
+    serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
+    serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0');
+
+    if (serverMajor < 3) {
+      throw new Exception("RFB server does not support protocol version 3");
+    }
+  }
+
+
+  //
+  // Write our protocol version message
+  //
+
+  void writeVersionMsg() throws IOException {
+    clientMajor = 3;
+    if (serverMajor > 3 || serverMinor >= 8) {
+      clientMinor = 8;
+      os.write(versionMsg_3_8.getBytes());
+    } else if (serverMinor >= 7) {
+      clientMinor = 7;
+      os.write(versionMsg_3_7.getBytes());
+    } else {
+      clientMinor = 3;
+      os.write(versionMsg_3_3.getBytes());
+    }
+    protocolTightVNC = false;
+    initCapabilities();
+  }
+
+
+  //
+  // Negotiate the authentication scheme.
+  //
+
+  int negotiateSecurity() throws Exception {
+    return (clientMinor >= 7) ?
+      selectSecurityType() : readSecurityType();
+  }
+
+  //
+  // Read security type from the server (protocol version 3.3).
+  //
+
+  int readSecurityType() throws Exception {
+    int secType = readU32();
+
+    switch (secType) {
+    case SecTypeInvalid:
+      readConnFailedReason();
+      return SecTypeInvalid;	// should never be executed
+    case SecTypeNone:
+    case SecTypeVncAuth:
+      return secType;
+    default:
+      throw new Exception("Unknown security type from RFB server: " + secType);
+    }
+  }
+
+  //
+  // Select security type from the server's list (protocol versions 3.7/3.8).
+  //
+
+  int selectSecurityType() throws Exception {
+    int secType = SecTypeInvalid;
+
+    // Read the list of secutiry types.
+    int nSecTypes = readU8();
+    if (nSecTypes == 0) {
+      readConnFailedReason();
+      return SecTypeInvalid;	// should never be executed
+    }
+    byte[] secTypes = new byte[nSecTypes];
+    readFully(secTypes);
+
+    // Find out if the server supports TightVNC protocol extensions
+    for (int i = 0; i < nSecTypes; i++) {
+      if (secTypes[i] == SecTypeTight) {
+	protocolTightVNC = true;
+	os.write(SecTypeTight);
+	return SecTypeTight;
+      }
+    }
+
+    // Find first supported security type.
+    for (int i = 0; i < nSecTypes; i++) {
+      if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) {
+	secType = secTypes[i];
+	break;
+      }
+    }
+
+    if (secType == SecTypeInvalid) {
+      throw new Exception("Server did not offer supported security type");
+    } else {
+      os.write(secType);
+    }
+
+    return secType;
+  }
+
+  //
+  // Perform "no authentication".
+  //
+
+  void authenticateNone() throws Exception {
+    if (clientMinor >= 8)
+      readSecurityResult("No authentication");
+  }
+
+  //
+  // Perform standard VNC Authentication.
+  //
+
+  void authenticateVNC(String pw) throws Exception {
+    byte[] challenge = new byte[16];
+    readFully(challenge);
+
+    if (pw.length() > 8)
+      pw = pw.substring(0, 8);	// Truncate to 8 chars
+
+    // Truncate password on the first zero byte.
+    int firstZero = pw.indexOf(0);
+    if (firstZero != -1)
+      pw = pw.substring(0, firstZero);
+
+    byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
+    System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
+
+    DesCipher des = new DesCipher(key);
+
+    des.encrypt(challenge, 0, challenge, 0);
+    des.encrypt(challenge, 8, challenge, 8);
+
+    os.write(challenge);
+
+    readSecurityResult("VNC authentication");
+  }
+
+  //
+  // Read security result.
+  // Throws an exception on authentication failure.
+  //
+
+  void readSecurityResult(String authType) throws Exception {
+    int securityResult = readU32();
+
+    switch (securityResult) {
+    case VncAuthOK:
+      System.out.println(authType + ": success");
+      break;
+    case VncAuthFailed:
+      if (clientMinor >= 8)
+        readConnFailedReason();
+      throw new Exception(authType + ": failed");
+    case VncAuthTooMany:
+      throw new Exception(authType + ": failed, too many tries");
+    default:
+      throw new Exception(authType + ": unknown result " + securityResult);
+    }
+  }
+
+  //
+  // Read the string describing the reason for a connection failure,
+  // and throw an exception.
+  //
+
+  void readConnFailedReason() throws Exception {
+    int reasonLen = readU32();
+    byte[] reason = new byte[reasonLen];
+    readFully(reason);
+    throw new Exception(new String(reason));
+  }
+
+  //
+  // Initialize capability lists (TightVNC protocol extensions).
+  //
+
+  void initCapabilities() {
+    tunnelCaps    = new CapsContainer();
+    authCaps      = new CapsContainer();
+    serverMsgCaps = new CapsContainer();
+    clientMsgCaps = new CapsContainer();
+    encodingCaps  = new CapsContainer();
+
+    // Supported authentication methods
+    authCaps.add(AuthNone, StandardVendor, SigAuthNone,
+		 "No authentication");
+    authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
+		 "Standard VNC password authentication");
+
+    // Supported non-standard server-to-client messages
+    // [NONE]
+
+    // Supported non-standard client-to-server messages
+    // [NONE]
+
+    // Supported encoding types
+    encodingCaps.add(EncodingCopyRect, StandardVendor,
+		     SigEncodingCopyRect, "Standard CopyRect encoding");
+    encodingCaps.add(EncodingRRE, StandardVendor,
+		     SigEncodingRRE, "Standard RRE encoding");
+    encodingCaps.add(EncodingCoRRE, StandardVendor,
+		     SigEncodingCoRRE, "Standard CoRRE encoding");
+    encodingCaps.add(EncodingHextile, StandardVendor,
+		     SigEncodingHextile, "Standard Hextile encoding");
+    encodingCaps.add(EncodingZRLE, StandardVendor,
+		     SigEncodingZRLE, "Standard ZRLE encoding");
+    encodingCaps.add(EncodingZlib, TridiaVncVendor,
+		     SigEncodingZlib, "Zlib encoding");
+    encodingCaps.add(EncodingTight, TightVncVendor,
+		     SigEncodingTight, "Tight encoding");
+
+    // Supported pseudo-encoding types
+    encodingCaps.add(EncodingCompressLevel0, TightVncVendor,
+		     SigEncodingCompressLevel0, "Compression level");
+    encodingCaps.add(EncodingQualityLevel0, TightVncVendor,
+		     SigEncodingQualityLevel0, "JPEG quality level");
+    encodingCaps.add(EncodingXCursor, TightVncVendor,
+		     SigEncodingXCursor, "X-style cursor shape update");
+    encodingCaps.add(EncodingRichCursor, TightVncVendor,
+		     SigEncodingRichCursor, "Rich-color cursor shape update");
+    encodingCaps.add(EncodingPointerPos, TightVncVendor,
+		     SigEncodingPointerPos, "Pointer position update");
+    encodingCaps.add(EncodingLastRect, TightVncVendor,
+		     SigEncodingLastRect, "LastRect protocol extension");
+    encodingCaps.add(EncodingNewFBSize, TightVncVendor,
+		     SigEncodingNewFBSize, "Framebuffer size change");
+  }
+
+  //
+  // Setup tunneling (TightVNC protocol extensions)
+  //
+
+  void setupTunneling() throws IOException {
+    int nTunnelTypes = readU32();
+    if (nTunnelTypes != 0) {
+      readCapabilityList(tunnelCaps, nTunnelTypes);
+
+      // We don't support tunneling yet.
+      writeInt(NoTunneling);
+    }
+  }
+
+  //
+  // Negotiate authentication scheme (TightVNC protocol extensions)
+  //
+
+  int negotiateAuthenticationTight() throws Exception {
+    int nAuthTypes = readU32();
+    if (nAuthTypes == 0)
+      return AuthNone;
+
+    readCapabilityList(authCaps, nAuthTypes);
+    for (int i = 0; i < authCaps.numEnabled(); i++) {
+      int authType = authCaps.getByOrder(i);
+      if (authType == AuthNone || authType == AuthVNC) {
+	writeInt(authType);
+	return authType;
+      }
+    }
+    throw new Exception("No suitable authentication scheme found");
+  }
+
+  //
+  // Read a capability list (TightVNC protocol extensions)
+  //
+
+  void readCapabilityList(CapsContainer caps, int count) throws IOException {
+    int code;
+    byte[] vendor = new byte[4];
+    byte[] name = new byte[8];
+    for (int i = 0; i < count; i++) {
+      code = readU32();
+      readFully(vendor);
+      readFully(name);
+      caps.enable(new CapabilityInfo(code, vendor, name));
+    }
+  }
+
+  //
+  // Write a 32-bit integer into the output stream.
+  //
+
+  void writeInt(int value) throws IOException {
+    byte[] b = new byte[4];
+    b[0] = (byte) ((value >> 24) & 0xff);
+    b[1] = (byte) ((value >> 16) & 0xff);
+    b[2] = (byte) ((value >> 8) & 0xff);
+    b[3] = (byte) (value & 0xff);
+    os.write(b);
+  }
+
+  //
+  // Write the client initialisation message
+  //
+
+  void writeClientInit() throws IOException {
+    if (viewer.options.shareDesktop) {
+      os.write(1);
+    } else {
+      os.write(0);
+    }
+    viewer.options.disableShareDesktop();
+  }
+
+
+  //
+  // Read the server initialisation message
+  //
+
+  String desktopName;
+  int framebufferWidth, framebufferHeight;
+  int bitsPerPixel, depth;
+  boolean bigEndian, trueColour;
+  int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
+
+  void readServerInit() throws IOException {
+    framebufferWidth = readU16();
+    framebufferHeight = readU16();
+    bitsPerPixel = readU8();
+    depth = readU8();
+    bigEndian = (readU8() != 0);
+    trueColour = (readU8() != 0);
+    redMax = readU16();
+    greenMax = readU16();
+    blueMax = readU16();
+    redShift = readU8();
+    greenShift = readU8();
+    blueShift = readU8();
+    byte[] pad = new byte[3];
+    readFully(pad);
+    int nameLength = readU32();
+    byte[] name = new byte[nameLength];
+    readFully(name);
+    desktopName = new String(name);
+
+    // Read interaction capabilities (TightVNC protocol extensions)
+    if (protocolTightVNC) {
+      int nServerMessageTypes = readU16();
+      int nClientMessageTypes = readU16();
+      int nEncodingTypes = readU16();
+      readU16();
+      readCapabilityList(serverMsgCaps, nServerMessageTypes);
+      readCapabilityList(clientMsgCaps, nClientMessageTypes);
+      readCapabilityList(encodingCaps, nEncodingTypes);
+    }
+
+    inNormalProtocol = true;
+  }
+
+
+  //
+  // Create session file and write initial protocol messages into it.
+  //
+
+  void startSession(String fname) throws IOException {
+    rec = new SessionRecorder(fname);
+    rec.writeHeader();
+    rec.write(versionMsg_3_3.getBytes());
+    rec.writeIntBE(SecTypeNone);
+    rec.writeShortBE(framebufferWidth);
+    rec.writeShortBE(framebufferHeight);
+    byte[] fbsServerInitMsg =	{
+      32, 24, 0, 1, 0,
+      (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF,
+      16, 8, 0, 0, 0, 0
+    };
+    rec.write(fbsServerInitMsg);
+    rec.writeIntBE(desktopName.length());
+    rec.write(desktopName.getBytes());
+    numUpdatesInSession = 0;
+
+    // FIXME: If there were e.g. ZRLE updates only, that should not
+    //        affect recording of Zlib and Tight updates. So, actually
+    //        we should maintain separate flags for Zlib, ZRLE and
+    //        Tight, instead of one ``wereZlibUpdates'' variable.
+    //
+    if (wereZlibUpdates)
+      recordFromBeginning = false;
+
+    zlibWarningShown = false;
+    tightWarningShown = false;
+  }
+
+  //
+  // Close session file.
+  //
+
+  void closeSession() throws IOException {
+    if (rec != null) {
+      rec.close();
+      rec = null;
+    }
+  }
+
+
+  //
+  // Set new framebuffer size
+  //
+
+  void setFramebufferSize(int width, int height) {
+    framebufferWidth = width;
+    framebufferHeight = height;
+  }
+
+
+  //
+  // Read the server message type
+  //
+
+  int readServerMessageType() throws IOException {
+    int msgType = readU8();
+
+    // If the session is being recorded:
+    if (rec != null) {
+      if (msgType == Bell) {	// Save Bell messages in session files.
+	rec.writeByte(msgType);
+	if (numUpdatesInSession > 0)
+	  rec.flush();
+      }
+    }
+
+    return msgType;
+  }
+
+
+  //
+  // Read a FramebufferUpdate message
+  //
+
+  int updateNRects;
+
+  void readFramebufferUpdate() throws IOException {
+    skipBytes(1);
+    updateNRects = readU16();
+//    System.out.println(updateNRects);
+    
+    // If the session is being recorded:
+    if (rec != null) {
+      rec.writeByte(FramebufferUpdate);
+      rec.writeByte(0);
+      rec.writeShortBE(updateNRects);
+    }
+
+    numUpdatesInSession++;
+  }
+
+  // Read a FramebufferUpdate rectangle header
+
+  int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
+
+  void readFramebufferUpdateRectHdr() throws Exception {
+    updateRectX = readU16();
+    updateRectY = readU16();
+    updateRectW = readU16();
+    updateRectH = readU16();
+    updateRectEncoding = readU32();
+//    System.out.println("readU16&32");
+
+    if (updateRectEncoding == EncodingZlib ||
+        updateRectEncoding == EncodingZRLE ||
+	updateRectEncoding == EncodingTight)
+      wereZlibUpdates = true;
+
+    // If the session is being recorded:
+    if (rec != null) {
+      if (numUpdatesInSession > 1)
+	rec.flush();		// Flush the output on each rectangle.
+      rec.writeShortBE(updateRectX);
+      rec.writeShortBE(updateRectY);
+      rec.writeShortBE(updateRectW);
+      rec.writeShortBE(updateRectH);
+      if (updateRectEncoding == EncodingZlib && !recordFromBeginning) {
+	// Here we cannot write Zlib-encoded rectangles because the
+	// decoder won't be able to reproduce zlib stream state.
+	if (!zlibWarningShown) {
+	  System.out.println("Warning: Raw encoding will be used " +
+			     "instead of Zlib in recorded session.");
+	  zlibWarningShown = true;
+	}
+	rec.writeIntBE(EncodingRaw);
+      } else {
+	rec.writeIntBE(updateRectEncoding);
+	if (updateRectEncoding == EncodingTight && !recordFromBeginning &&
+	    !tightWarningShown) {
+	  System.out.println("Warning: Re-compressing Tight-encoded " +
+			     "updates for session recording.");
+	  tightWarningShown = true;
+	}
+      }
+    }
+
+    if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding)
+      return;
+
+    if (updateRectX + updateRectW > framebufferWidth ||
+	updateRectY + updateRectH > framebufferHeight) {
+      throw new Exception("Framebuffer update rectangle too large: " +
+			  updateRectW + "x" + updateRectH + " at (" +
+			  updateRectX + "," + updateRectY + ")");
+    }
+  }
+
+  // Read CopyRect source X and Y.
+
+  int copyRectSrcX, copyRectSrcY;
+
+  void readCopyRect() throws IOException {
+	copyRectSrcX = readU16();
+    copyRectSrcY = readU16();
+
+    // If the session is being recorded:
+    if (rec != null) {
+      rec.writeShortBE(copyRectSrcX);
+      rec.writeShortBE(copyRectSrcY);
+    }
+  }
+
+
+  //
+  // Read a ServerCutText message
+  //
+
+  String readServerCutText() throws IOException {
+    skipBytes(3);
+    int len = readU32();
+    byte[] text = new byte[len];
+    readFully(text);
+    return new String(text);
+  }
+
+
+  //
+  // Read an integer in compact representation (1..3 bytes).
+  // Such format is used as a part of the Tight encoding.
+  // Also, this method records data if session recording is active and
+  // the viewer's recordFromBeginning variable is set to true.
+  //
+
+  int readCompactLen() throws IOException {
+    int[] portion = new int[3];
+    portion[0] = readU8();
+    int byteCount = 1;
+    int len = portion[0] & 0x7F;
+    if ((portion[0] & 0x80) != 0) {
+      portion[1] = readU8();
+      byteCount++;
+      len |= (portion[1] & 0x7F) << 7;
+      if ((portion[1] & 0x80) != 0) {
+	portion[2] = readU8();
+	byteCount++;
+	len |= (portion[2] & 0xFF) << 14;
+      }
+    }
+
+    if (rec != null && recordFromBeginning)
+      for (int i = 0; i < byteCount; i++)
+	rec.writeByte(portion[i]);
+
+    return len;
+  }
+
+
+  //
+  // Write a FramebufferUpdateRequest message
+  //
+
+  void writeFramebufferUpdateRequest(int x, int y, int w, int h,
+				     boolean incremental)
+       throws IOException
+  {
+    byte[] b = new byte[10];
+
+    b[0] = (byte) FramebufferUpdateRequest;
+    b[1] = (byte) (incremental ? 1 : 0);
+    b[2] = (byte) ((x >> 8) & 0xff);
+    b[3] = (byte) (x & 0xff);
+    b[4] = (byte) ((y >> 8) & 0xff);
+    b[5] = (byte) (y & 0xff);
+    b[6] = (byte) ((w >> 8) & 0xff);
+    b[7] = (byte) (w & 0xff);
+    b[8] = (byte) ((h >> 8) & 0xff);
+    b[9] = (byte) (h & 0xff);
+
+    os.write(b);
+  }
+
+
+  //
+  // Write a SetPixelFormat message
+  //
+
+  void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian,
+			   boolean trueColour,
+			   int redMax, int greenMax, int blueMax,
+			   int redShift, int greenShift, int blueShift)
+       throws IOException
+  {
+    byte[] b = new byte[20];
+
+    b[0]  = (byte) SetPixelFormat;
+    b[4]  = (byte) bitsPerPixel;
+    b[5]  = (byte) depth;
+    b[6]  = (byte) (bigEndian ? 1 : 0);
+    b[7]  = (byte) (trueColour ? 1 : 0);
+    b[8]  = (byte) ((redMax >> 8) & 0xff);
+    b[9]  = (byte) (redMax & 0xff);
+    b[10] = (byte) ((greenMax >> 8) & 0xff);
+    b[11] = (byte) (greenMax & 0xff);
+    b[12] = (byte) ((blueMax >> 8) & 0xff);
+    b[13] = (byte) (blueMax & 0xff);
+    b[14] = (byte) redShift;
+    b[15] = (byte) greenShift;
+    b[16] = (byte) blueShift;
+
+    os.write(b);
+  }
+
+
+  //
+  // Write a FixColourMapEntries message.  The values in the red, green and
+  // blue arrays are from 0 to 65535.
+  //
+
+  void writeFixColourMapEntries(int firstColour, int nColours,
+				int[] red, int[] green, int[] blue)
+       throws IOException
+  {
+    byte[] b = new byte[6 + nColours * 6];
+
+    b[0] = (byte) FixColourMapEntries;
+    b[2] = (byte) ((firstColour >> 8) & 0xff);
+    b[3] = (byte) (firstColour & 0xff);
+    b[4] = (byte) ((nColours >> 8) & 0xff);
+    b[5] = (byte) (nColours & 0xff);
+
+    for (int i = 0; i < nColours; i++) {
+      b[6 + i * 6]     = (byte) ((red[i] >> 8) & 0xff);
+      b[6 + i * 6 + 1] = (byte) (red[i] & 0xff);
+      b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff);
+      b[6 + i * 6 + 3] = (byte) (green[i] & 0xff);
+      b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff);
+      b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff);
+    }
+ 
+    os.write(b);
+  }
+
+
+  //
+  // Write a SetEncodings message
+  //
+
+  void writeSetEncodings(int[] encs, int len) throws IOException {
+    byte[] b = new byte[4 + 4 * len];
+
+    b[0] = (byte) SetEncodings;
+    b[2] = (byte) ((len >> 8) & 0xff);
+    b[3] = (byte) (len & 0xff);
+
+    for (int i = 0; i < len; i++) {
+      b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff);
+      b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff);
+      b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff);
+      b[7 + 4 * i] = (byte) (encs[i] & 0xff);
+    }
+
+    os.write(b);
+  }
+
+
+  //
+  // Write a ClientCutText message
+  //
+
+  void writeClientCutText(String text) throws IOException {
+    byte[] b = new byte[8 + text.length()];
+
+    b[0] = (byte) ClientCutText;
+    b[4] = (byte) ((text.length() >> 24) & 0xff);
+    b[5] = (byte) ((text.length() >> 16) & 0xff);
+    b[6] = (byte) ((text.length() >> 8) & 0xff);
+    b[7] = (byte) (text.length() & 0xff);
+
+    System.arraycopy(text.getBytes(), 0, b, 8, text.length());
+
+    os.write(b);
+  }
+
+
+  //
+  // A buffer for putting pointer and keyboard events before being sent.  This
+  // is to ensure that multiple RFB events generated from a single Java Event 
+  // will all be sent in a single network packet.  The maximum possible
+  // length is 4 modifier down events, a single key event followed by 4
+  // modifier up events i.e. 9 key events or 72 bytes.
+  //
+
+  byte[] eventBuf = new byte[72];
+  int eventBufLen;
+
+
+  // Useful shortcuts for modifier masks.
+
+  final static int CTRL_MASK  = InputEvent.CTRL_MASK;
+  final static int SHIFT_MASK = InputEvent.SHIFT_MASK;
+  final static int META_MASK  = InputEvent.META_MASK;
+  final static int ALT_MASK   = InputEvent.ALT_MASK;
+
+
+  //
+  // Write a pointer event message.  We may need to send modifier key events
+  // around it to set the correct modifier state.
+  //
+
+  int pointerMask = 0;
+
+  void writePointerEvent(MouseEvent evt) throws IOException {
+    int modifiers = evt.getModifiers();
+
+    int mask2 = 2;
+    int mask3 = 4;
+    if (viewer.options.reverseMouseButtons2And3) {
+      mask2 = 4;
+      mask3 = 2;
+    }
+
+    // Note: For some reason, AWT does not set BUTTON1_MASK on left
+    // button presses. Here we think that it was the left button if
+    // modifiers do not include BUTTON2_MASK or BUTTON3_MASK.
+
+    if (evt.getID() == MouseEvent.MOUSE_PRESSED) {
+      if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
+        pointerMask = mask2;
+        modifiers &= ~ALT_MASK;
+      } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
+        pointerMask = mask3;
+        modifiers &= ~META_MASK;
+      } else {
+        pointerMask = 1;
+      }
+    } else if (evt.getID() == MouseEvent.MOUSE_RELEASED) {
+      pointerMask = 0;
+      if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
+        modifiers &= ~ALT_MASK;
+      } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
+        modifiers &= ~META_MASK;
+      }
+    }
+
+    eventBufLen = 0;
+    writeModifierKeyEvents(modifiers);
+
+    int x = evt.getX();
+    int y = evt.getY();
+
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+
+    eventBuf[eventBufLen++] = (byte) PointerEvent;
+    eventBuf[eventBufLen++] = (byte) pointerMask;
+    eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
+    eventBuf[eventBufLen++] = (byte) (x & 0xff);
+    eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
+    eventBuf[eventBufLen++] = (byte) (y & 0xff);
+
+    //
+    // Always release all modifiers after an "up" event
+    //
+
+    if (pointerMask == 0) {
+      writeModifierKeyEvents(0);
+    }
+
+    os.write(eventBuf, 0, eventBufLen);
+  }
+
+
+  //
+  // Write a key event message.  We may need to send modifier key events
+  // around it to set the correct modifier state.  Also we need to translate
+  // from the Java key values to the X keysym values used by the RFB protocol.
+  //
+
+  void writeKeyEvent(KeyEvent evt) throws IOException {
+
+    int keyChar = evt.getKeyChar();
+
+    //
+    // Ignore event if only modifiers were pressed.
+    //
+
+    // Some JVMs return 0 instead of CHAR_UNDEFINED in getKeyChar().
+    if (keyChar == 0)
+      keyChar = KeyEvent.CHAR_UNDEFINED;
+
+    if (keyChar == KeyEvent.CHAR_UNDEFINED) {
+      int code = evt.getKeyCode();
+      if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT ||
+          code == KeyEvent.VK_META || code == KeyEvent.VK_ALT)
+        return;
+    }
+
+    //
+    // Key press or key release?
+    //
+
+    boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
+
+    int key;
+    if (evt.isActionKey()) {
+
+      //
+      // An action key should be one of the following.
+      // If not then just ignore the event.
+      //
+
+      switch(evt.getKeyCode()) {
+      case KeyEvent.VK_HOME:      key = 0xff50; break;
+      case KeyEvent.VK_LEFT:      key = 0xff51; break;
+      case KeyEvent.VK_UP:        key = 0xff52; break;
+      case KeyEvent.VK_RIGHT:     key = 0xff53; break;
+      case KeyEvent.VK_DOWN:      key = 0xff54; break;
+      case KeyEvent.VK_PAGE_UP:   key = 0xff55; break;
+      case KeyEvent.VK_PAGE_DOWN: key = 0xff56; break;
+      case KeyEvent.VK_END:       key = 0xff57; break;
+      case KeyEvent.VK_INSERT:    key = 0xff63; break;
+      case KeyEvent.VK_F1:        key = 0xffbe; break;
+      case KeyEvent.VK_F2:        key = 0xffbf; break;
+      case KeyEvent.VK_F3:        key = 0xffc0; break;
+      case KeyEvent.VK_F4:        key = 0xffc1; break;
+      case KeyEvent.VK_F5:        key = 0xffc2; break;
+      case KeyEvent.VK_F6:        key = 0xffc3; break;
+      case KeyEvent.VK_F7:        key = 0xffc4; break;
+      case KeyEvent.VK_F8:        key = 0xffc5; break;
+      case KeyEvent.VK_F9:        key = 0xffc6; break;
+      case KeyEvent.VK_F10:       key = 0xffc7; break;
+      case KeyEvent.VK_F11:       key = 0xffc8; break;
+      case KeyEvent.VK_F12:       key = 0xffc9; break;
+      default:
+        return;
+      }
+
+    } else {
+
+      //
+      // A "normal" key press.  Ordinary ASCII characters go straight through.
+      // For CTRL-<letter>, CTRL is sent separately so just send <letter>.
+      // Backspace, tab, return, escape and delete have special keysyms.
+      // Anything else we ignore.
+      //
+
+      key = keyChar;
+
+      if (key < 0x20) {
+        if (evt.isControlDown()) {
+          key += 0x60;
+        } else {
+          switch(key) {
+          case KeyEvent.VK_BACK_SPACE: key = 0xff08; break;
+          case KeyEvent.VK_TAB:        key = 0xff09; break;
+          case KeyEvent.VK_ENTER:      key = 0xff0d; break;
+          case KeyEvent.VK_ESCAPE:     key = 0xff1b; break;
+          }
+        }
+      } else if (key == 0x7f) {
+	// Delete
+	key = 0xffff;
+      } else if (key > 0xff) {
+	// JDK1.1 on X incorrectly passes some keysyms straight through,
+	// so we do too.  JDK1.1.4 seems to have fixed this.
+	// The keysyms passed are 0xff00 .. XK_BackSpace .. XK_Delete
+	// Also, we pass through foreign currency keysyms (0x20a0..0x20af).
+	if ((key < 0xff00 || key > 0xffff) &&
+	    !(key >= 0x20a0 && key <= 0x20af))
+	  return;
+      }
+    }
+
+    // Fake keyPresses for keys that only generates keyRelease events
+    if ((key == 0xe5) || (key == 0xc5) || // XK_aring / XK_Aring
+	(key == 0xe4) || (key == 0xc4) || // XK_adiaeresis / XK_Adiaeresis
+	(key == 0xf6) || (key == 0xd6) || // XK_odiaeresis / XK_Odiaeresis
+	(key == 0xa7) || (key == 0xbd) || // XK_section / XK_onehalf
+	(key == 0xa3)) {                  // XK_sterling
+      // Make sure we do not send keypress events twice on platforms
+      // with correct JVMs (those that actually report KeyPress for all
+      // keys)	
+      if (down)
+	brokenKeyPressed = true;
+
+      if (!down && !brokenKeyPressed) {
+	// We've got a release event for this key, but haven't received
+        // a press. Fake it. 
+	eventBufLen = 0;
+	writeModifierKeyEvents(evt.getModifiers());
+	writeKeyEvent(key, true);
+	os.write(eventBuf, 0, eventBufLen);
+      }
+
+      if (!down)
+	brokenKeyPressed = false;  
+    }
+
+    eventBufLen = 0;
+    writeModifierKeyEvents(evt.getModifiers());
+    writeKeyEvent(key, down);
+
+    // Always release all modifiers after an "up" event
+    if (!down)
+      writeModifierKeyEvents(0);
+
+    os.write(eventBuf, 0, eventBufLen);
+  }
+
+
+  //
+  // Add a raw key event with the given X keysym to eventBuf.
+  //
+
+  void writeKeyEvent(int keysym, boolean down) {
+    eventBuf[eventBufLen++] = (byte) KeyboardEvent;
+    eventBuf[eventBufLen++] = (byte) (down ? 1 : 0);
+    eventBuf[eventBufLen++] = (byte) 0;
+    eventBuf[eventBufLen++] = (byte) 0;
+    eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff);
+    eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff);
+    eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff);
+    eventBuf[eventBufLen++] = (byte) (keysym & 0xff);
+  }
+
+
+  //
+  // Write key events to set the correct modifier state.
+  //
+
+  int oldModifiers = 0;
+
+  void writeModifierKeyEvents(int newModifiers) {
+    if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
+      writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
+
+    if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK))
+      writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0);
+
+    if ((newModifiers & META_MASK) != (oldModifiers & META_MASK))
+      writeKeyEvent(0xffe7, (newModifiers & META_MASK) != 0);
+
+    if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK))
+      writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0);
+
+    oldModifiers = newModifiers;
+  }
+
+  //
+  // Compress and write the data into the recorded session file. This
+  // method assumes the recording is on (rec != null).
+  //
+
+  void recordCompressedData(byte[] data, int off, int len) throws IOException {
+    Deflater deflater = new Deflater();
+    deflater.setInput(data, off, len);
+    int bufSize = len + len / 100 + 12;
+    byte[] buf = new byte[bufSize];
+    deflater.finish();
+    int compressedSize = deflater.deflate(buf);
+    recordCompactLen(compressedSize);
+    rec.write(buf, 0, compressedSize);
+  }
+
+  void recordCompressedData(byte[] data) throws IOException {
+    recordCompressedData(data, 0, data.length);
+  }
+
+  //
+  // Write an integer in compact representation (1..3 bytes) into the
+  // recorded session file. This method assumes the recording is on
+  // (rec != null).
+  //
+
+  void recordCompactLen(int len) throws IOException {
+    byte[] buf = new byte[3];
+    int bytes = 0;
+    buf[bytes++] = (byte)(len & 0x7F);
+    if (len > 0x7F) {
+      buf[bytes-1] |= 0x80;
+      buf[bytes++] = (byte)(len >> 7 & 0x7F);
+      if (len > 0x3FFF) {
+	buf[bytes-1] |= 0x80;
+	buf[bytes++] = (byte)(len >> 14 & 0xFF);
+      }
+    }
+    rec.write(buf, 0, bytes);
+  }
+
+  public void startTiming() {
+    timing = true;
+
+    // Carry over up to 1s worth of previous rate for smoothing.
+
+    if (timeWaitedIn100us > 10000) {
+      timedKbits = timedKbits * 10000 / timeWaitedIn100us;
+      timeWaitedIn100us = 10000;
+    }
+  }
+
+  public void stopTiming() {
+    timing = false; 
+    if (timeWaitedIn100us < timedKbits/2)
+      timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
+  }
+
+  public long kbitsPerSecond() {
+    return timedKbits * 10000 / timeWaitedIn100us;
+  }
+
+  public long timeWaited() {
+    return timeWaitedIn100us;
+  }
+
+  //
+  // Methods for reading data via our DataInputStream member variable (is).
+  //
+  // In addition to reading data, the readFully() methods updates variables
+  // used to estimate data throughput.
+  //
+
+  public void readFully(byte b[]) throws IOException {
+    readFully(b, 0, b.length);
+  }
+
+  public void readFully(byte b[], int off, int len) throws IOException {
+    long before = 0;
+    if (timing)
+      before = System.currentTimeMillis();
+
+    is.readFully(b, off, len);
+
+    if (timing) {
+      long after = System.currentTimeMillis();
+      long newTimeWaited = (after - before) * 10;
+      int newKbits = len * 8 / 1000;
+
+      // limit rate to between 10kbit/s and 40Mbit/s
+
+      if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
+      if (newTimeWaited < newKbits/4)    newTimeWaited = newKbits/4;
+
+      timeWaitedIn100us += newTimeWaited;
+      timedKbits += newKbits;
+    }
+
+    numBytesRead += len;
+  }
+
+  final int available() throws IOException {
+    return is.available();
+  }
+
+  // FIXME: DataInputStream::skipBytes() is not guaranteed to skip
+  //        exactly n bytes. Probably we don't want to use this method.
+  final int skipBytes(int n) throws IOException {
+    int r = is.skipBytes(n);
+    numBytesRead += r;
+    return r;
+  }
+
+  final int readU8() throws IOException {
+    int r = is.readUnsignedByte();
+    numBytesRead++;
+
+    return r;
+  }
+
+  final int readU16() throws IOException {
+    int r = is.readUnsignedShort();
+    numBytesRead += 2;
+    return r;
+  }
+
+  final int readU32() throws IOException {
+    int r = is.readInt();
+    numBytesRead += 4;
+    return r;
+  }
+  void mark(int len) throws IOException{
+  	  is.mark(len);
+  }
+  void reset() throws IOException{
+  	  is.reset();
+  }
+  final boolean markSupported() {
+	  return is.markSupported();
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/SessionRecorder.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,193 @@
+//
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// SessionRecorder is a class to write FBS (FrameBuffer Stream) files.
+// FBS files are used to save RFB sessions for later playback.
+//
+
+import java.io.*;
+
+class SessionRecorder {
+
+  protected FileOutputStream f;
+  protected DataOutputStream df;
+  protected long startTime, lastTimeOffset;
+
+  protected byte[] buffer;
+  protected int bufferSize;
+  protected int bufferBytes;
+
+  public SessionRecorder(String name, int bufsize) throws IOException {
+    f = new FileOutputStream(name);
+    df = new DataOutputStream(f);
+    startTime = System.currentTimeMillis();
+    lastTimeOffset = 0;
+    
+    bufferSize = bufsize;
+    bufferBytes = 0;
+    buffer = new byte[bufferSize];
+  }
+
+  public SessionRecorder(String name) throws IOException {
+    this(name, 65536);
+  }
+
+  //
+  // Close the file, free resources.
+  //
+
+  public void close() throws IOException {
+    try {
+      flush();
+    } catch (IOException e) {
+    }
+
+    df = null;
+    f.close();
+    f = null;
+    buffer = null;
+  }
+
+  //
+  // Write the FBS file header as defined in the rfbproxy utility.
+  //
+
+  public void writeHeader() throws IOException {
+    df.write("FBS 001.000\n".getBytes());
+  }
+
+  //
+  // Write one byte.
+  //
+
+  public void writeByte(int b) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes++] = (byte)b;
+  }
+
+  //
+  // Write 16-bit value, big-endian.
+  //
+
+  public void writeShortBE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes++] = (byte)(v >> 8);
+    buffer[bufferBytes++] = (byte)v;
+  }
+
+  //
+  // Write 32-bit value, big-endian.
+  //
+
+  public void writeIntBE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes]     = (byte)(v >> 24);
+    buffer[bufferBytes + 1] = (byte)(v >> 16);
+    buffer[bufferBytes + 2] = (byte)(v >> 8);
+    buffer[bufferBytes + 3] = (byte)v;
+    bufferBytes += 4;
+  }
+
+  //
+  // Write 16-bit value, little-endian.
+  //
+
+  public void writeShortLE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes++] = (byte)v;
+    buffer[bufferBytes++] = (byte)(v >> 8);
+  }
+
+  //
+  // Write 32-bit value, little-endian.
+  //
+
+  public void writeIntLE(int v) throws IOException {
+    prepareWriting();
+    buffer[bufferBytes]     = (byte)v;
+    buffer[bufferBytes + 1] = (byte)(v >> 8);
+    buffer[bufferBytes + 2] = (byte)(v >> 16);
+    buffer[bufferBytes + 3] = (byte)(v >> 24);
+    bufferBytes += 4;
+  }
+
+  //
+  // Write byte arrays.
+  //
+
+  public void write(byte b[], int off, int len) throws IOException {
+    prepareWriting();
+    while (len > 0) {
+      if (bufferBytes > bufferSize - 4)
+	flush(false);
+
+      int partLen;
+      if (bufferBytes + len > bufferSize) {
+	partLen = bufferSize - bufferBytes;
+      } else {
+	partLen = len;
+      }
+      System.arraycopy(b, off, buffer, bufferBytes, partLen);
+      bufferBytes += partLen;
+      off += partLen;
+      len -= partLen;
+    }
+  }
+
+  public void write(byte b[]) throws IOException {
+    write(b, 0, b.length);
+  }
+
+  //
+  // Flush the output. This method saves buffered data in the
+  // underlying file object adding data sizes and timestamps. If the
+  // updateTimeOffset is set to false, then the current time offset
+  // will not be changed for next write operation.
+  //
+
+  public void flush(boolean updateTimeOffset) throws IOException {
+    if (bufferBytes > 0) {
+      df.writeInt(bufferBytes);
+      df.write(buffer, 0, (bufferBytes + 3) & 0x7FFFFFFC);
+      df.writeInt((int)lastTimeOffset);
+      bufferBytes = 0;
+      if (updateTimeOffset)
+	lastTimeOffset = -1;
+    }
+  }
+
+  public void flush() throws IOException {
+    flush(true);
+  }
+
+  //
+  // Before writing any data, remember time offset and flush the
+  // buffer before it becomes full.
+  //
+
+  protected void prepareWriting() throws IOException {
+    if (lastTimeOffset == -1)
+      lastTimeOffset = System.currentTimeMillis() - startTime;
+    if (bufferBytes > bufferSize - 4)
+      flush(false);
+  }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/SocketFactory.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,36 @@
+//
+//  Copyright (C) 2002 HorizonLive.com, Inc.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// SocketFactory.java describes an interface used to substitute the
+// standard Socket class by its alternative implementations.
+//
+
+import java.applet.*;
+import java.net.*;
+import java.io.*;
+
+public interface SocketFactory {
+
+  public Socket createSocket(String host, int port, Applet applet)
+    throws IOException;
+
+  public Socket createSocket(String host, int port, String[] args)
+    throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/VncCanvas.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,1968 @@
+//
+//  Copyright (C) 2004 Horizon Wimba.  All Rights Reserved.
+//  Copyright (C) 2001-2003 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2001,2002 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.io.*;
+import java.lang.*;
+import java.nio.ByteBuffer;
+import java.util.zip.*;
+
+
+//
+// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
+//
+
+class VncCanvas extends Canvas
+  implements KeyListener, MouseListener, MouseMotionListener {
+
+  VncViewer viewer;
+  RfbProto rfb;
+  ColorModel cm8, cm24;
+  Color[] colors;
+  int bytesPixel;
+
+  int maxWidth = 0, maxHeight = 0;
+  int scalingFactor;
+  int scaledWidth, scaledHeight;
+
+  Image memImage;
+  Graphics memGraphics;
+
+  Image rawPixelsImage;
+  MemoryImageSource pixelsSource;
+  byte[] pixels8;
+  int[] pixels24;
+
+  // Update statistics.
+  long statStartTime;           // time on first framebufferUpdateRequest
+  int statNumUpdates;           // counter for FramebufferUpdate messages
+  int statNumTotalRects;        // rectangles in FramebufferUpdate messages
+  int statNumPixelRects;        // the same, but excluding pseudo-rectangles
+  int statNumRectsTight;        // Tight-encoded rectangles (including JPEG)
+  int statNumRectsTightJPEG;    // JPEG-compressed Tight-encoded rectangles
+  int statNumRectsZRLE;         // ZRLE-encoded rectangles
+  int statNumRectsHextile;      // Hextile-encoded rectangles
+  int statNumRectsRaw;          // Raw-encoded rectangles
+  int statNumRectsCopy;         // CopyRect rectangles
+  int statNumBytesEncoded;      // number of bytes in updates, as received
+  int statNumBytesDecoded;      // number of bytes, as if Raw encoding was used
+
+  // ZRLE encoder's data.
+  byte[] zrleBuf;
+  int zrleBufLen = 0;
+  byte[] zrleTilePixels8;
+  int[] zrleTilePixels24;
+  ZlibInStream zrleInStream;
+  boolean zrleRecWarningShown = false;
+
+  // Zlib encoder's data.
+  byte[] zlibBuf;
+  int zlibBufLen = 0;
+  Inflater zlibInflater;
+
+  // Tight encoder's data.
+  final static int tightZlibBufferSize = 512;
+  Inflater[] tightInflaters;
+
+  // Since JPEG images are loaded asynchronously, we have to remember
+  // their position in the framebuffer. Also, this jpegRect object is
+  // used for synchronization between the rfbThread and a JVM's thread
+  // which decodes and loads JPEG images.
+  Rectangle jpegRect;
+
+  // True if we process keyboard and mouse events.
+  boolean inputEnabled;
+
+  //
+  // The constructors.
+  //
+
+  public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
+    throws IOException {
+
+    viewer = v;
+    maxWidth = maxWidth_;
+    maxHeight = maxHeight_;
+
+    rfb = viewer.rfb;
+    scalingFactor = viewer.options.scalingFactor;
+
+    tightInflaters = new Inflater[4];
+
+    cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
+    cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
+
+    colors = new Color[256];
+    for (int i = 0; i < 256; i++)
+      colors[i] = new Color(cm8.getRGB(i));
+
+    setPixelFormat();
+
+    inputEnabled = false;
+    if (!viewer.options.viewOnly)
+      enableInput(true);
+
+    // Keyboard listener is enabled even in view-only mode, to catch
+    // 'r' or 'R' key presses used to request screen update.
+    addKeyListener(this);
+  }
+
+  public VncCanvas(VncViewer v) throws IOException {
+    this(v, 0, 0);
+  }
+
+  //
+  // Callback methods to determine geometry of our Component.
+  //
+
+  public Dimension getPreferredSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  public Dimension getMinimumSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  public Dimension getMaximumSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  //
+  // All painting is performed here.
+  //
+
+  public void update(Graphics g) {
+    paint(g);
+  }
+
+  public void paint(Graphics g) {
+    synchronized(memImage) {
+      if (rfb.framebufferWidth == scaledWidth) {
+        g.drawImage(memImage, 0, 0, null);
+      } else {
+        paintScaledFrameBuffer(g);
+      }
+    }
+    if (showSoftCursor) {
+      int x0 = cursorX - hotX, y0 = cursorY - hotY;
+      Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
+      if (r.intersects(g.getClipBounds())) {
+	g.drawImage(softCursor, x0, y0, null);
+      }
+    }
+  }
+
+  public void paintScaledFrameBuffer(Graphics g) {
+    g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
+  }
+
+  //
+  // Override the ImageObserver interface method to handle drawing of
+  // JPEG-encoded data.
+  //
+
+  public boolean imageUpdate(Image img, int infoflags,
+                             int x, int y, int width, int height) {
+    if ((infoflags & (ALLBITS | ABORT)) == 0) {
+      return true;		// We need more image data.
+    } else {
+      // If the whole image is available, draw it now.
+      if ((infoflags & ALLBITS) != 0) {
+	if (jpegRect != null) {
+	  synchronized(jpegRect) {
+	    memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
+	    scheduleRepaint(jpegRect.x, jpegRect.y,
+			    jpegRect.width, jpegRect.height);
+	    jpegRect.notify();
+	  }
+	}
+      }
+      return false;		// All image data was processed.
+    }
+  }
+
+  //
+  // Start/stop receiving mouse events. Keyboard events are received
+  // even in view-only mode, because we want to map the 'r' key to the
+  // screen refreshing function.
+  //
+
+  public synchronized void enableInput(boolean enable) {
+    if (enable && !inputEnabled) {
+      inputEnabled = true;
+      addMouseListener(this);
+      addMouseMotionListener(this);
+      if (viewer.showControls) {
+	viewer.buttonPanel.enableRemoteAccessControls(true);
+      }
+      createSoftCursor();	// scaled cursor
+    } else if (!enable && inputEnabled) {
+      inputEnabled = false;
+      removeMouseListener(this);
+      removeMouseMotionListener(this);
+      if (viewer.showControls) {
+	viewer.buttonPanel.enableRemoteAccessControls(false);
+      }
+      createSoftCursor();	// non-scaled cursor
+    }
+  }
+
+  public void setPixelFormat() throws IOException {
+    if (viewer.options.eightBitColors) {
+      rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
+      bytesPixel = 1;
+    } else {
+      rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
+      bytesPixel = 4;
+    }
+    updateFramebufferSize();
+  }
+
+  void updateFramebufferSize() {
+
+    // Useful shortcuts.
+    int fbWidth = rfb.framebufferWidth;
+    int fbHeight = rfb.framebufferHeight;
+
+    // Calculate scaling factor for auto scaling.
+    if (maxWidth > 0 && maxHeight > 0) {
+      int f1 = maxWidth * 100 / fbWidth;
+      int f2 = maxHeight * 100 / fbHeight;
+      scalingFactor = Math.min(f1, f2);
+      if (scalingFactor > 100)
+	scalingFactor = 100;
+      System.out.println("Scaling desktop at " + scalingFactor + "%");
+    }
+
+    // Update scaled framebuffer geometry.
+    scaledWidth = (fbWidth * scalingFactor + 50) / 100;
+    scaledHeight = (fbHeight * scalingFactor + 50) / 100;
+
+    // Create new off-screen image either if it does not exist, or if
+    // its geometry should be changed. It's not necessary to replace
+    // existing image if only pixel format should be changed.
+    if (memImage == null) {
+      memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
+      memGraphics = memImage.getGraphics();
+    } else if (memImage.getWidth(null) != fbWidth ||
+	       memImage.getHeight(null) != fbHeight) {
+      synchronized(memImage) {
+	memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
+	memGraphics = memImage.getGraphics();
+      }
+    }
+
+    // Images with raw pixels should be re-allocated on every change
+    // of geometry or pixel format.
+    if (bytesPixel == 1) {
+
+      pixels24 = null;
+      pixels8 = new byte[fbWidth * fbHeight];
+
+      pixelsSource =
+	new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
+
+      zrleTilePixels24 = null;
+      zrleTilePixels8 = new byte[64 * 64];
+
+    } else {
+
+      pixels8 = null;
+      pixels24 = new int[fbWidth * fbHeight];
+
+      pixelsSource =
+	new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
+
+      zrleTilePixels8 = null;
+      zrleTilePixels24 = new int[64 * 64];
+
+    }
+    pixelsSource.setAnimated(true);
+    rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
+
+    // Update the size of desktop containers.
+    if (viewer.inSeparateFrame) {
+      if (viewer.desktopScrollPane != null)
+	resizeDesktopFrame();
+    } else {
+      setSize(scaledWidth, scaledHeight);
+    }
+    viewer.moveFocusToDesktop();
+  }
+
+  void resizeDesktopFrame() {
+    setSize(scaledWidth, scaledHeight);
+
+    // FIXME: Find a better way to determine correct size of a
+    // ScrollPane.  -- const
+    Insets insets = viewer.desktopScrollPane.getInsets();
+    viewer.desktopScrollPane.setSize(scaledWidth +
+				     2 * Math.min(insets.left, insets.right),
+				     scaledHeight +
+				     2 * Math.min(insets.top, insets.bottom));
+
+    viewer.vncFrame.pack();
+
+    // Try to limit the frame size to the screen size.
+
+    Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
+    Dimension frameSize = viewer.vncFrame.getSize();
+    Dimension newSize = frameSize;
+
+    // Reduce Screen Size by 30 pixels in each direction;
+    // This is a (poor) attempt to account for
+    //     1) Menu bar on Macintosh (should really also account for
+    //        Dock on OSX).  Usually 22px on top of screen.
+    //     2) Taxkbar on Windows (usually about 28 px on bottom)
+    //     3) Other obstructions.
+
+    screenSize.height -= 30;
+    screenSize.width  -= 30;
+
+    boolean needToResizeFrame = false;
+    if (frameSize.height > screenSize.height) {
+      newSize.height = screenSize.height;
+      needToResizeFrame = true;
+    }
+    if (frameSize.width > screenSize.width) {
+      newSize.width = screenSize.width;
+      needToResizeFrame = true;
+    }
+    if (needToResizeFrame) {
+      viewer.vncFrame.setSize(newSize);
+    }
+
+    viewer.desktopScrollPane.doLayout();
+  }
+
+  //
+  // processNormalProtocol() - executed by the rfbThread to deal with the
+  // RFB socket.
+  //
+
+  public void processNormalProtocol() throws Exception {
+
+    // Start/stop session recording if necessary.
+    viewer.checkRecordingStatus();
+
+    rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+				      rfb.framebufferHeight, false);
+
+    resetStats();
+    boolean statsRestarted = false;
+
+    //
+    // main dispatch loop
+    //
+
+    long count = 0;
+
+    while (true) {
+
+    	  if(rfb.TEST){
+    		  	System.out.println("");
+    	      	int len = rfb.available();
+    			System.out.println("rfb.available()="+len);
+    	      	int skiplen = 0;
+    	      	if(len > 0){
+
+    	    		if(count > 0) skiplen = rfb.rnBytes + 20;
+    	    			  
+    		    	int nBytes = 0;
+    	    		rfb.mark(20);
+    	    		int msgType = rfb.readU8();
+    	    		System.out.println("msgType="+msgType);
+
+    	    		rfb.skipBytes(15);
+//    	    		rfb.skipBytes(16);
+    	    		nBytes = rfb.readU32();
+    	    		System.out.println("nBytes="+nBytes);
+    	    		rfb.reset();
+
+
+    	    		if(nBytes > 0){//0より大きい(データがある)ときデータを転送
+        	    		rfb.buffer = ByteBuffer.allocate(len); //使ってない
+
+        	    		rfb.mark(nBytes+20);
+
+    	    			System.out.println("rfb.rnBytes="+rfb.rnBytes);
+    	   	    	
+    	    			byte b[] = new byte[nBytes+20];
+    	    			//    	  		  	byte b[] = new byte[18+rfb.rnBytes];
+    	    			System.out.println("b.length="+b.length);
+    	   	    	
+    	   	    	
+    	    			rfb.readFully(b);
+
+    	    			if(rfb.markSupported()) 	
+    	    				System.out.println("markSupported = true");
+    	    			else
+    	    				System.out.println("markSupported = false");
+    	    			
+    	    			rfb.cliSock.getOutputStream().write(b, 0, b.length);    
+
+    	    			try{	
+    	    				rfb.reset();
+    	    			}catch(IOException e){System.out.println(e);}
+    	    		}
+    	    		
+
+
+
+    	  	    	/*
+    	  	    	boolean fullUpdateNeeded = false;
+
+    	  	    	// Start/stop session recording if necessary. Request full
+    	  	    	// update if a new session file was opened.
+    	  	    	//	    		if (viewer.checkRecordingStatus())
+    	  	    	  fullUpdateNeeded = true;
+    	    		  	
+    	  	    		if (viewer.options.eightBitColors != (bytesPixel == 1)) {
+    	  	    	          // Pixel format should be changed.
+    	  	    	          setPixelFormat();
+    	  	    	          fullUpdateNeeded = true;
+    	  	    		}
+
+    	  	    	        // Request framebuffer update if needed.
+    	  	    	        int w = rfb.framebufferWidth;
+    	  	    	        int h = rfb.framebufferHeight;
+    	  	    	        rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded);
+*/    	    		  
+
+    	    		  }
+//    	    		  continue;
+    	    	  } 
+    	    	  count++;
+    	
+
+  	  
+  	  
+      // Read message type from the server.
+      int msgType = rfb.readServerMessageType();
+      
+      
+      // Process the message depending on its type.
+      switch (msgType) { 
+      case RfbProto.FramebufferUpdate:
+
+        if (statNumUpdates == viewer.debugStatsExcludeUpdates &&
+            !statsRestarted) {
+          resetStats();
+          statsRestarted = true;
+        } else if (statNumUpdates == viewer.debugStatsMeasureUpdates &&
+                   statsRestarted) {
+          viewer.disconnect();
+        }
+
+	rfb.readFramebufferUpdate();
+	statNumUpdates++;
+
+	
+	boolean cursorPosReceived = false;
+
+	for (int i = 0; i < rfb.updateNRects; i++) {
+
+	  rfb.readFramebufferUpdateRectHdr();
+	  statNumTotalRects++;
+	  int rx = rfb.updateRectX, ry = rfb.updateRectY;
+	  int rw = rfb.updateRectW, rh = rfb.updateRectH;
+
+		if(rfb.TEST){
+			System.out.println("updateRectEncoding="+rfb.updateRectEncoding);
+		}
+	  
+	  
+	  if (rfb.updateRectEncoding == rfb.EncodingLastRect)
+	    break;
+
+	  if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
+	    rfb.setFramebufferSize(rw, rh);
+	    updateFramebufferSize();
+	    break;
+	  }
+
+	  if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
+	      rfb.updateRectEncoding == rfb.EncodingRichCursor) {
+	    handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
+	    continue;
+	  }
+
+	  if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
+	    softCursorMove(rx, ry);
+	    cursorPosReceived = true;
+	    continue;
+	  }
+
+          long numBytesReadBefore = rfb.getNumBytesRead();
+
+          rfb.startTiming();
+
+	  switch (rfb.updateRectEncoding) {
+	  case RfbProto.EncodingRaw:
+	    statNumRectsRaw++;
+	    handleRawRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingCopyRect:
+	    statNumRectsCopy++;
+	    handleCopyRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingRRE:
+	    handleRRERect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingCoRRE:
+	    handleCoRRERect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingHextile:
+	    statNumRectsHextile++;
+	    handleHextileRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingZRLE:
+	    statNumRectsZRLE++;
+	    handleZRLERect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingZlib:
+            handleZlibRect(rx, ry, rw, rh);
+	    break;
+	  case RfbProto.EncodingTight:
+	    statNumRectsTight++;
+	    handleTightRect(rx, ry, rw, rh);
+	    break;
+	  default:
+	    throw new Exception("Unknown RFB rectangle encoding " +
+				rfb.updateRectEncoding);
+	  }
+
+          rfb.stopTiming();
+
+          statNumPixelRects++;
+          statNumBytesDecoded += rw * rh * bytesPixel;
+          statNumBytesEncoded +=
+            (int)(rfb.getNumBytesRead() - numBytesReadBefore);
+	}
+
+	boolean fullUpdateNeeded = false;
+	
+	
+
+	// Start/stop session recording if necessary. Request full
+	// update if a new session file was opened.
+	if (viewer.checkRecordingStatus())
+	  fullUpdateNeeded = true;
+
+	// Defer framebuffer update request if necessary. But wake up
+	// immediately on keyboard or mouse event. Also, don't sleep
+	// if there is some data to receive, or if the last update
+	// included a PointerPos message.
+	if (viewer.deferUpdateRequests > 0 &&
+	    rfb.available() == 0 && !cursorPosReceived) {
+	  synchronized(rfb) {
+	    try {
+	      rfb.wait(viewer.deferUpdateRequests);
+	    } catch (InterruptedException e) {
+	    }
+	  }
+	}
+
+        viewer.autoSelectEncodings();
+
+	// Before requesting framebuffer update, check if the pixel
+	// format should be changed.
+	if (viewer.options.eightBitColors != (bytesPixel == 1)) {
+          // Pixel format should be changed.
+          setPixelFormat();
+          fullUpdateNeeded = true;
+	}
+
+        // Request framebuffer update if needed.
+        int w = rfb.framebufferWidth;
+        int h = rfb.framebufferHeight;
+        rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded);
+
+	break;
+
+      case RfbProto.SetColourMapEntries:
+	throw new Exception("Can't handle SetColourMapEntries message");
+
+      case RfbProto.Bell:
+        Toolkit.getDefaultToolkit().beep();
+	break;
+
+      case RfbProto.ServerCutText:
+	String s = rfb.readServerCutText();
+	viewer.clipboard.setCutText(s);
+	break;
+
+      default:
+	throw new Exception("Unknown RFB message type " + msgType);
+      }
+      
+
+    
+    
+    
+    
+    }
+  }
+
+
+  //
+  // Handle a raw rectangle. The second form with paint==false is used
+  // by the Hextile decoder for raw-encoded tiles.
+  //
+
+  void handleRawRect(int x, int y, int w, int h) throws IOException {
+    handleRawRect(x, y, w, h, true);
+  }
+
+  void handleRawRect(int x, int y, int w, int h, boolean paint)
+    throws IOException {
+
+    if (bytesPixel == 1) {
+      for (int dy = y; dy < y + h; dy++) {
+	rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
+	if (rfb.rec != null) {
+	  rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+	}
+      }
+    } else {
+      byte[] buf = new byte[w * 4];
+      int i, offset;
+      for (int dy = y; dy < y + h; dy++) {
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+	  rfb.rec.write(buf);
+	}
+	offset = dy * rfb.framebufferWidth + x;
+	for (i = 0; i < w; i++) {
+	  pixels24[offset + i] =
+	    (buf[i * 4 + 2] & 0xFF) << 16 |
+	    (buf[i * 4 + 1] & 0xFF) << 8 |
+	    (buf[i * 4] & 0xFF);
+	}
+      }
+    }
+
+    handleUpdatedPixels(x, y, w, h);
+    if (paint)
+      scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a CopyRect rectangle.
+  //
+
+  void handleCopyRect(int x, int y, int w, int h) throws IOException {
+
+    rfb.readCopyRect();
+    memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
+			 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle an RRE-encoded rectangle.
+  //
+
+  void handleRRERect(int x, int y, int w, int h) throws IOException {
+
+    int nSubrects = rfb.readU32();
+
+    byte[] bg_buf = new byte[bytesPixel];
+    rfb.readFully(bg_buf);
+    Color pixel;
+    if (bytesPixel == 1) {
+      pixel = colors[bg_buf[0] & 0xFF];
+    } else {
+      pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+    }
+    memGraphics.setColor(pixel);
+    memGraphics.fillRect(x, y, w, h);
+
+    byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
+    rfb.readFully(buf);
+    DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
+
+    if (rfb.rec != null) {
+      rfb.rec.writeIntBE(nSubrects);
+      rfb.rec.write(bg_buf);
+      rfb.rec.write(buf);
+    }
+
+    int sx, sy, sw, sh;
+
+    for (int j = 0; j < nSubrects; j++) {
+      if (bytesPixel == 1) {
+	pixel = colors[ds.readUnsignedByte()];
+      } else {
+	ds.skip(4);
+	pixel = new Color(buf[j*12+2] & 0xFF,
+			  buf[j*12+1] & 0xFF,
+			  buf[j*12]   & 0xFF);
+      }
+      sx = x + ds.readUnsignedShort();
+      sy = y + ds.readUnsignedShort();
+      sw = ds.readUnsignedShort();
+      sh = ds.readUnsignedShort();
+
+      memGraphics.setColor(pixel);
+      memGraphics.fillRect(sx, sy, sw, sh);
+    }
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a CoRRE-encoded rectangle.
+  //
+
+  void handleCoRRERect(int x, int y, int w, int h) throws IOException {
+    int nSubrects = rfb.readU32();
+
+    byte[] bg_buf = new byte[bytesPixel];
+    rfb.readFully(bg_buf);
+    Color pixel;
+    if (bytesPixel == 1) {
+      pixel = colors[bg_buf[0] & 0xFF];
+    } else {
+      pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
+    }
+    memGraphics.setColor(pixel);
+    memGraphics.fillRect(x, y, w, h);
+
+    byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
+    rfb.readFully(buf);
+
+    if (rfb.rec != null) {
+      rfb.rec.writeIntBE(nSubrects);
+      rfb.rec.write(bg_buf);
+      rfb.rec.write(buf);
+    }
+
+    int sx, sy, sw, sh;
+    int i = 0;
+
+    for (int j = 0; j < nSubrects; j++) {
+      if (bytesPixel == 1) {
+	pixel = colors[buf[i++] & 0xFF];
+      } else {
+	pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
+	i += 4;
+      }
+      sx = x + (buf[i++] & 0xFF);
+      sy = y + (buf[i++] & 0xFF);
+      sw = buf[i++] & 0xFF;
+      sh = buf[i++] & 0xFF;
+
+      memGraphics.setColor(pixel);
+      memGraphics.fillRect(sx, sy, sw, sh);
+    }
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a Hextile-encoded rectangle.
+  //
+
+  // These colors should be kept between handleHextileSubrect() calls.
+  private Color hextile_bg, hextile_fg;
+
+  void handleHextileRect(int x, int y, int w, int h) throws IOException {
+
+    hextile_bg = new Color(0);
+    hextile_fg = new Color(0);
+
+    for (int ty = y; ty < y + h; ty += 16) {
+      int th = 16;
+      if (y + h - ty < 16)
+	th = y + h - ty;
+
+      for (int tx = x; tx < x + w; tx += 16) {
+	int tw = 16;
+	if (x + w - tx < 16)
+	  tw = x + w - tx;
+
+	handleHextileSubrect(tx, ty, tw, th);
+      }
+
+      // Finished with a row of tiles, now let's show it.
+      scheduleRepaint(x, y, w, h);
+    }
+  }
+
+  //
+  // Handle one tile in the Hextile-encoded data.
+  //
+
+  void handleHextileSubrect(int tx, int ty, int tw, int th)
+    throws IOException {
+
+    int subencoding = rfb.readU8();
+    if (rfb.rec != null) {
+      rfb.rec.writeByte(subencoding);
+    }
+
+    // Is it a raw-encoded sub-rectangle?
+    if ((subencoding & rfb.HextileRaw) != 0) {
+      handleRawRect(tx, ty, tw, th, false);
+      return;
+    }
+
+    // Read and draw the background if specified.
+    byte[] cbuf = new byte[bytesPixel];
+    if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
+      rfb.readFully(cbuf);
+      if (bytesPixel == 1) {
+	hextile_bg = colors[cbuf[0] & 0xFF];
+      } else {
+	hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
+      }
+      if (rfb.rec != null) {
+	rfb.rec.write(cbuf);
+      }
+    }
+    memGraphics.setColor(hextile_bg);
+    memGraphics.fillRect(tx, ty, tw, th);
+
+    // Read the foreground color if specified.
+    if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
+      rfb.readFully(cbuf);
+      if (bytesPixel == 1) {
+	hextile_fg = colors[cbuf[0] & 0xFF];
+      } else {
+	hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
+      }
+      if (rfb.rec != null) {
+	rfb.rec.write(cbuf);
+      }
+    }
+
+    // Done with this tile if there is no sub-rectangles.
+    if ((subencoding & rfb.HextileAnySubrects) == 0)
+      return;
+
+    int nSubrects = rfb.readU8();
+    int bufsize = nSubrects * 2;
+    if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
+      bufsize += nSubrects * bytesPixel;
+    }
+    byte[] buf = new byte[bufsize];
+    rfb.readFully(buf);
+    if (rfb.rec != null) {
+      rfb.rec.writeByte(nSubrects);
+      rfb.rec.write(buf);
+    }
+
+    int b1, b2, sx, sy, sw, sh;
+    int i = 0;
+
+    if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
+
+      // Sub-rectangles are all of the same color.
+      memGraphics.setColor(hextile_fg);
+      for (int j = 0; j < nSubrects; j++) {
+	b1 = buf[i++] & 0xFF;
+	b2 = buf[i++] & 0xFF;
+	sx = tx + (b1 >> 4);
+	sy = ty + (b1 & 0xf);
+	sw = (b2 >> 4) + 1;
+	sh = (b2 & 0xf) + 1;
+	memGraphics.fillRect(sx, sy, sw, sh);
+      }
+    } else if (bytesPixel == 1) {
+
+      // BGR233 (8-bit color) version for colored sub-rectangles.
+      for (int j = 0; j < nSubrects; j++) {
+	hextile_fg = colors[buf[i++] & 0xFF];
+	b1 = buf[i++] & 0xFF;
+	b2 = buf[i++] & 0xFF;
+	sx = tx + (b1 >> 4);
+	sy = ty + (b1 & 0xf);
+	sw = (b2 >> 4) + 1;
+	sh = (b2 & 0xf) + 1;
+	memGraphics.setColor(hextile_fg);
+	memGraphics.fillRect(sx, sy, sw, sh);
+      }
+
+    } else {
+
+      // Full-color (24-bit) version for colored sub-rectangles.
+      for (int j = 0; j < nSubrects; j++) {
+	hextile_fg = new Color(buf[i+2] & 0xFF,
+			       buf[i+1] & 0xFF,
+			       buf[i] & 0xFF);
+	i += 4;
+	b1 = buf[i++] & 0xFF;
+	b2 = buf[i++] & 0xFF;
+	sx = tx + (b1 >> 4);
+	sy = ty + (b1 & 0xf);
+	sw = (b2 >> 4) + 1;
+	sh = (b2 & 0xf) + 1;
+	memGraphics.setColor(hextile_fg);
+	memGraphics.fillRect(sx, sy, sw, sh);
+      }
+
+    }
+  }
+
+  //
+  // Handle a ZRLE-encoded rectangle.
+  //
+  // FIXME: Currently, session recording is not fully supported for ZRLE.
+  //
+
+  void handleZRLERect(int x, int y, int w, int h) throws Exception {
+
+    if (zrleInStream == null)
+      zrleInStream = new ZlibInStream();
+
+    int nBytes = rfb.readU32();
+    if (nBytes > 64 * 1024 * 1024)
+      throw new Exception("ZRLE decoder: illegal compressed data size");
+
+    if (zrleBuf == null || zrleBufLen < nBytes) {
+      zrleBufLen = nBytes + 4096;
+      zrleBuf = new byte[zrleBufLen];
+    }
+
+     // FIXME: Do not wait for all the data before decompression.
+    rfb.readFully(zrleBuf, 0, nBytes);
+    
+    rfb.rnBytes = nBytes;
+
+    if (rfb.rec != null) {
+      if (rfb.recordFromBeginning) {
+        rfb.rec.writeIntBE(nBytes);
+        rfb.rec.write(zrleBuf, 0, nBytes);
+      } else if (!zrleRecWarningShown) {
+        System.out.println("Warning: ZRLE session can be recorded" +
+                           " only from the beginning");
+        System.out.println("Warning: Recorded file may be corrupted");
+        zrleRecWarningShown = true;
+      }
+
+    }
+
+    zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
+
+    for (int ty = y; ty < y+h; ty += 64) {
+
+      int th = Math.min(y+h-ty, 64);
+
+      for (int tx = x; tx < x+w; tx += 64) {
+
+        int tw = Math.min(x+w-tx, 64);
+
+        int mode = zrleInStream.readU8();
+        boolean rle = (mode & 128) != 0;
+        int palSize = mode & 127;
+        int[] palette = new int[128];
+
+        readZrlePalette(palette, palSize);
+
+        if (palSize == 1) {
+          int pix = palette[0];
+          Color c = (bytesPixel == 1) ?
+            colors[pix] : new Color(0xFF000000 | pix);
+          memGraphics.setColor(c);
+          memGraphics.fillRect(tx, ty, tw, th);
+          continue;
+        }
+
+        if (!rle) {
+          if (palSize == 0) {
+            readZrleRawPixels(tw, th);
+          } else {
+            readZrlePackedPixels(tw, th, palette, palSize);
+          }
+        } else {
+          if (palSize == 0) {
+            readZrlePlainRLEPixels(tw, th);
+          } else {
+            readZrlePackedRLEPixels(tw, th, palette);
+          }
+        }
+        handleUpdatedZrleTile(tx, ty, tw, th);
+      }
+    }
+
+    zrleInStream.reset();
+
+    scheduleRepaint(x, y, w, h);
+  }
+
+  int readPixel(InStream is) throws Exception {
+    int pix;,
+    
+    if (bytesPixel == 1) {
+
+    	
+    	pix = is.readU8();
+    } else {
+      int p1 = is.readU8();
+      int p2 = is.readU8();
+      int p3 = is.readU8();
+      pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
+    }
+    return pix;
+  }
+
+  void readPixels(InStream is, int[] dst, int count) throws Exception {
+    int pix;
+    if (bytesPixel == 1) {
+      byte[] buf = new byte[count];
+      is.readBytes(buf, 0, count);
+      for (int i = 0; i < count; i++) {
+        dst[i] = (int)buf[i] & 0xFF;
+      }
+    } else {
+      byte[] buf = new byte[count * 3];
+      is.readBytes(buf, 0, count * 3);
+      for (int i = 0; i < count; i++) {
+        dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
+                  (buf[i*3+1] & 0xFF) << 8 |
+                  (buf[i*3] & 0xFF));
+      }
+    }
+  }
+
+  void readZrlePalette(int[] palette, int palSize) throws Exception {
+    readPixels(zrleInStream, palette, palSize);
+  }
+
+  void readZrleRawPixels(int tw, int th) throws Exception {
+    if (bytesPixel == 1) {
+      zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
+    } else {
+      readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
+    }
+  }
+
+  void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
+    throws Exception {
+
+    int bppp = ((palSize > 16) ? 8 :
+                ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
+    int ptr = 0;
+
+    for (int i = 0; i < th; i++) {
+      int eol = ptr + tw;
+      int b = 0;
+      int nbits = 0;
+
+      while (ptr < eol) {
+        if (nbits == 0) {
+          b = zrleInStream.readU8();
+          nbits = 8;
+        }
+        nbits -= bppp;
+        int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
+        if (bytesPixel == 1) {
+          zrleTilePixels8[ptr++] = (byte)palette[index];
+        } else {
+          zrleTilePixels24[ptr++] = palette[index];
+        }
+      }
+    }
+  }
+
+  void readZrlePlainRLEPixels(int tw, int th) throws Exception {
+    int ptr = 0;
+    int end = ptr + tw * th;
+    while (ptr < end) {
+      int pix = readPixel(zrleInStream);
+      int len = 1;
+      int b;
+      do {
+        b = zrleInStream.readU8();
+        len += b;
+      } while (b == 255);
+
+      if (!(len <= end - ptr))
+        throw new Exception("ZRLE decoder: assertion failed" +
+                            " (len <= end-ptr)");
+
+      if (bytesPixel == 1) {
+        while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
+      } else {
+        while (len-- > 0) zrleTilePixels24[ptr++] = pix;
+      }
+    }
+  }
+
+  void readZrlePackedRLEPixels(int tw, int th, int[] palette)
+    throws Exception {
+
+    int ptr = 0;
+    int end = ptr + tw * th;
+    while (ptr < end) {
+      int index = zrleInStream.readU8();
+      int len = 1;
+      if ((index & 128) != 0) {
+        int b;
+        do {
+          b = zrleInStream.readU8();
+          len += b;
+        } while (b == 255);
+        
+        if (!(len <= end - ptr))
+          throw new Exception("ZRLE decoder: assertion failed" +
+                              " (len <= end - ptr)");
+      }
+
+      index &= 127;
+      int pix = palette[index];
+
+      if (bytesPixel == 1) {
+        while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
+      } else {
+        while (len-- > 0) zrleTilePixels24[ptr++] = pix;
+      }
+    }
+  }
+
+  //
+  // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
+  //
+
+  void handleUpdatedZrleTile(int x, int y, int w, int h) {
+    Object src, dst;
+    if (bytesPixel == 1) {
+      src = zrleTilePixels8; dst = pixels8;
+    } else {
+      src = zrleTilePixels24; dst = pixels24;
+    }
+    int offsetSrc = 0;
+    int offsetDst = (y * rfb.framebufferWidth + x);
+    for (int j = 0; j < h; j++) {
+      System.arraycopy(src, offsetSrc, dst, offsetDst, w);
+      offsetSrc += w;
+      offsetDst += rfb.framebufferWidth;
+    }
+    handleUpdatedPixels(x, y, w, h);
+  }
+
+  //
+  // Handle a Zlib-encoded rectangle.
+  //
+
+  void handleZlibRect(int x, int y, int w, int h) throws Exception {
+
+    int nBytes = rfb.readU32();
+
+    if (zlibBuf == null || zlibBufLen < nBytes) {
+      zlibBufLen = nBytes * 2;
+      zlibBuf = new byte[zlibBufLen];
+    }
+
+    rfb.readFully(zlibBuf, 0, nBytes);
+
+    if (rfb.rec != null && rfb.recordFromBeginning) {
+      rfb.rec.writeIntBE(nBytes);
+      rfb.rec.write(zlibBuf, 0, nBytes);
+    }
+
+    if (zlibInflater == null) {
+      zlibInflater = new Inflater();
+    }
+    zlibInflater.setInput(zlibBuf, 0, nBytes);
+
+    if (bytesPixel == 1) {
+      for (int dy = y; dy < y + h; dy++) {
+	zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
+	if (rfb.rec != null && !rfb.recordFromBeginning)
+	  rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+      }
+    } else {
+      byte[] buf = new byte[w * 4];
+      int i, offset;
+      for (int dy = y; dy < y + h; dy++) {
+	zlibInflater.inflate(buf);
+	offset = dy * rfb.framebufferWidth + x;
+	for (i = 0; i < w; i++) {
+	  pixels24[offset + i] =
+	    (buf[i * 4 + 2] & 0xFF) << 16 |
+	    (buf[i * 4 + 1] & 0xFF) << 8 |
+	    (buf[i * 4] & 0xFF);
+	}
+	if (rfb.rec != null && !rfb.recordFromBeginning)
+	  rfb.rec.write(buf);
+      }
+    }
+
+    handleUpdatedPixels(x, y, w, h);
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Handle a Tight-encoded rectangle.
+  //
+
+  void handleTightRect(int x, int y, int w, int h) throws Exception {
+
+    int comp_ctl = rfb.readU8();
+    if (rfb.rec != null) {
+      if (rfb.recordFromBeginning ||
+	  comp_ctl == (rfb.TightFill << 4) ||
+	  comp_ctl == (rfb.TightJpeg << 4)) {
+	// Send data exactly as received.
+	rfb.rec.writeByte(comp_ctl);
+      } else {
+	// Tell the decoder to flush each of the four zlib streams.
+	rfb.rec.writeByte(comp_ctl | 0x0F);
+      }
+    }
+
+    // Flush zlib streams if we are told by the server to do so.
+    for (int stream_id = 0; stream_id < 4; stream_id++) {
+      if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
+	tightInflaters[stream_id] = null;
+      }
+      comp_ctl >>= 1;
+    }
+
+    // Check correctness of subencoding value.
+    if (comp_ctl > rfb.TightMaxSubencoding) {
+      throw new Exception("Incorrect tight subencoding: " + comp_ctl);
+    }
+
+    // Handle solid-color rectangles.
+    if (comp_ctl == rfb.TightFill) {
+
+      if (bytesPixel == 1) {
+	int idx = rfb.readU8();
+	memGraphics.setColor(colors[idx]);
+	if (rfb.rec != null) {
+	  rfb.rec.writeByte(idx);
+	}
+      } else {
+	byte[] buf = new byte[3];
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+	  rfb.rec.write(buf);
+	}
+	Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
+			     (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
+	memGraphics.setColor(bg);
+      }
+      memGraphics.fillRect(x, y, w, h);
+      scheduleRepaint(x, y, w, h);
+      return;
+
+    }
+
+    if (comp_ctl == rfb.TightJpeg) {
+
+      statNumRectsTightJPEG++;
+
+      // Read JPEG data.
+      byte[] jpegData = new byte[rfb.readCompactLen()];
+      rfb.readFully(jpegData);
+      if (rfb.rec != null) {
+	if (!rfb.recordFromBeginning) {
+	  rfb.recordCompactLen(jpegData.length);
+	}
+	rfb.rec.write(jpegData);
+      }
+
+      // Create an Image object from the JPEG data.
+      Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
+
+      // Remember the rectangle where the image should be drawn.
+      jpegRect = new Rectangle(x, y, w, h);
+
+      // Let the imageUpdate() method do the actual drawing, here just
+      // wait until the image is fully loaded and drawn.
+      synchronized(jpegRect) {
+	Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
+	try {
+	  // Wait no longer than three seconds.
+	  jpegRect.wait(3000);
+	} catch (InterruptedException e) {
+	  throw new Exception("Interrupted while decoding JPEG image");
+	}
+      }
+
+      // Done, jpegRect is not needed any more.
+      jpegRect = null;
+      return;
+
+    }
+
+    // Read filter id and parameters.
+    int numColors = 0, rowSize = w;
+    byte[] palette8 = new byte[2];
+    int[] palette24 = new int[256];
+    boolean useGradient = false;
+    if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
+      int filter_id = rfb.readU8();
+      if (rfb.rec != null) {
+	rfb.rec.writeByte(filter_id);
+      }
+      if (filter_id == rfb.TightFilterPalette) {
+	numColors = rfb.readU8() + 1;
+	if (rfb.rec != null) {
+	  rfb.rec.writeByte(numColors - 1);
+	}
+        if (bytesPixel == 1) {
+	  if (numColors != 2) {
+	    throw new Exception("Incorrect tight palette size: " + numColors);
+	  }
+	  rfb.readFully(palette8);
+	  if (rfb.rec != null) {
+	    rfb.rec.write(palette8);
+	  }
+	} else {
+	  byte[] buf = new byte[numColors * 3];
+	  rfb.readFully(buf);
+	  if (rfb.rec != null) {
+	    rfb.rec.write(buf);
+	  }
+	  for (int i = 0; i < numColors; i++) {
+	    palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
+			    (buf[i * 3 + 1] & 0xFF) << 8 |
+			    (buf[i * 3 + 2] & 0xFF));
+	  }
+	}
+	if (numColors == 2)
+	  rowSize = (w + 7) / 8;
+      } else if (filter_id == rfb.TightFilterGradient) {
+	useGradient = true;
+      } else if (filter_id != rfb.TightFilterCopy) {
+	throw new Exception("Incorrect tight filter id: " + filter_id);
+      }
+    }
+    if (numColors == 0 && bytesPixel == 4)
+      rowSize *= 3;
+
+    // Read, optionally uncompress and decode data.
+    int dataSize = h * rowSize;
+    if (dataSize < rfb.TightMinToCompress) {
+      // Data size is small - not compressed with zlib.
+      if (numColors != 0) {
+	// Indexed colors.
+	byte[] indexedData = new byte[dataSize];
+	rfb.readFully(indexedData);
+	if (rfb.rec != null) {
+	  rfb.rec.write(indexedData);
+	}
+	if (numColors == 2) {
+	  // Two colors.
+	  if (bytesPixel == 1) {
+	    decodeMonoData(x, y, w, h, indexedData, palette8);
+	  } else {
+	    decodeMonoData(x, y, w, h, indexedData, palette24);
+	  }
+	} else {
+	  // 3..255 colors (assuming bytesPixel == 4).
+	  int i = 0;
+	  for (int dy = y; dy < y + h; dy++) {
+	    for (int dx = x; dx < x + w; dx++) {
+	      pixels24[dy * rfb.framebufferWidth + dx] =
+		palette24[indexedData[i++] & 0xFF];
+	    }
+	  }
+	}
+      } else if (useGradient) {
+	// "Gradient"-processed data
+	byte[] buf = new byte[w * h * 3];
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+	  rfb.rec.write(buf);
+	}
+	decodeGradientData(x, y, w, h, buf);
+      } else {
+	// Raw truecolor data.
+	if (bytesPixel == 1) {
+	  for (int dy = y; dy < y + h; dy++) {
+	    rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
+	    if (rfb.rec != null) {
+	      rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+	    }
+	  }
+	} else {
+	  byte[] buf = new byte[w * 3];
+	  int i, offset;
+	  for (int dy = y; dy < y + h; dy++) {
+	    rfb.readFully(buf);
+	    if (rfb.rec != null) {
+	      rfb.rec.write(buf);
+	    }
+	    offset = dy * rfb.framebufferWidth + x;
+	    for (i = 0; i < w; i++) {
+	      pixels24[offset + i] =
+		(buf[i * 3] & 0xFF) << 16 |
+		(buf[i * 3 + 1] & 0xFF) << 8 |
+		(buf[i * 3 + 2] & 0xFF);
+	    }
+	  }
+	}
+      }
+    } else {
+      // Data was compressed with zlib.
+      int zlibDataLen = rfb.readCompactLen();
+      byte[] zlibData = new byte[zlibDataLen];
+      rfb.readFully(zlibData);
+      if (rfb.rec != null && rfb.recordFromBeginning) {
+	rfb.rec.write(zlibData);
+      }
+      int stream_id = comp_ctl & 0x03;
+      if (tightInflaters[stream_id] == null) {
+	tightInflaters[stream_id] = new Inflater();
+      }
+      Inflater myInflater = tightInflaters[stream_id];
+      myInflater.setInput(zlibData);
+      byte[] buf = new byte[dataSize];
+      myInflater.inflate(buf);
+      if (rfb.rec != null && !rfb.recordFromBeginning) {
+	rfb.recordCompressedData(buf);
+      }
+
+      if (numColors != 0) {
+	// Indexed colors.
+	if (numColors == 2) {
+	  // Two colors.
+	  if (bytesPixel == 1) {
+	    decodeMonoData(x, y, w, h, buf, palette8);
+	  } else {
+	    decodeMonoData(x, y, w, h, buf, palette24);
+	  }
+	} else {
+	  // More than two colors (assuming bytesPixel == 4).
+	  int i = 0;
+	  for (int dy = y; dy < y + h; dy++) {
+	    for (int dx = x; dx < x + w; dx++) {
+	      pixels24[dy * rfb.framebufferWidth + dx] =
+		palette24[buf[i++] & 0xFF];
+	    }
+	  }
+	}
+      } else if (useGradient) {
+	// Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
+	decodeGradientData(x, y, w, h, buf);
+      } else {
+	// Compressed truecolor data.
+	if (bytesPixel == 1) {
+	  int destOffset = y * rfb.framebufferWidth + x;
+	  for (int dy = 0; dy < h; dy++) {
+	    System.arraycopy(buf, dy * w, pixels8, destOffset, w);
+	    destOffset += rfb.framebufferWidth;
+	  }
+	} else {
+	  int srcOffset = 0;
+	  int destOffset, i;
+	  for (int dy = 0; dy < h; dy++) {
+	    myInflater.inflate(buf);
+	    destOffset = (y + dy) * rfb.framebufferWidth + x;
+	    for (i = 0; i < w; i++) {
+	      pixels24[destOffset + i] =
+		(buf[srcOffset] & 0xFF) << 16 |
+		(buf[srcOffset + 1] & 0xFF) << 8 |
+		(buf[srcOffset + 2] & 0xFF);
+	      srcOffset += 3;
+	    }
+	  }
+	}
+      }
+    }
+
+    handleUpdatedPixels(x, y, w, h);
+    scheduleRepaint(x, y, w, h);
+  }
+
+  //
+  // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
+  //
+
+  void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
+
+    int dx, dy, n;
+    int i = y * rfb.framebufferWidth + x;
+    int rowBytes = (w + 7) / 8;
+    byte b;
+
+    for (dy = 0; dy < h; dy++) {
+      for (dx = 0; dx < w / 8; dx++) {
+	b = src[dy*rowBytes+dx];
+	for (n = 7; n >= 0; n--)
+	  pixels8[i++] = palette[b >> n & 1];
+      }
+      for (n = 7; n >= 8 - w % 8; n--) {
+	pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
+      }
+      i += (rfb.framebufferWidth - w);
+    }
+  }
+
+  void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
+
+    int dx, dy, n;
+    int i = y * rfb.framebufferWidth + x;
+    int rowBytes = (w + 7) / 8;
+    byte b;
+
+    for (dy = 0; dy < h; dy++) {
+      for (dx = 0; dx < w / 8; dx++) {
+	b = src[dy*rowBytes+dx];
+	for (n = 7; n >= 0; n--)
+	  pixels24[i++] = palette[b >> n & 1];
+      }
+      for (n = 7; n >= 8 - w % 8; n--) {
+	pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
+      }
+      i += (rfb.framebufferWidth - w);
+    }
+  }
+
+  //
+  // Decode data processed with the "Gradient" filter.
+  //
+
+  void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
+
+    int dx, dy, c;
+    byte[] prevRow = new byte[w * 3];
+    byte[] thisRow = new byte[w * 3];
+    byte[] pix = new byte[3];
+    int[] est = new int[3];
+
+    int offset = y * rfb.framebufferWidth + x;
+
+    for (dy = 0; dy < h; dy++) {
+
+      /* First pixel in a row */
+      for (c = 0; c < 3; c++) {
+	pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
+	thisRow[c] = pix[c];
+      }
+      pixels24[offset++] =
+	(pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
+
+      /* Remaining pixels of a row */
+      for (dx = 1; dx < w; dx++) {
+	for (c = 0; c < 3; c++) {
+	  est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
+		    (prevRow[(dx-1) * 3 + c] & 0xFF));
+	  if (est[c] > 0xFF) {
+	    est[c] = 0xFF;
+	  } else if (est[c] < 0x00) {
+	    est[c] = 0x00;
+	  }
+	  pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
+	  thisRow[dx * 3 + c] = pix[c];
+	}
+	pixels24[offset++] =
+	  (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
+      }
+
+      System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
+      offset += (rfb.framebufferWidth - w);
+    }
+  }
+
+  //
+  // Display newly updated area of pixels.
+  //
+
+  void handleUpdatedPixels(int x, int y, int w, int h) {
+
+    // Draw updated pixels of the off-screen image.
+    pixelsSource.newPixels(x, y, w, h);
+    memGraphics.setClip(x, y, w, h);
+    memGraphics.drawImage(rawPixelsImage, 0, 0, null);
+    memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
+  }
+
+  //
+  // Tell JVM to repaint specified desktop area.
+  //
+
+  void scheduleRepaint(int x, int y, int w, int h) {
+    // Request repaint, deferred if necessary.
+    if (rfb.framebufferWidth == scaledWidth) {
+      repaint(viewer.deferScreenUpdates, x, y, w, h);
+    } else {
+      int sx = x * scalingFactor / 100;
+      int sy = y * scalingFactor / 100;
+      int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
+      int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
+      repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
+    }
+  }
+
+  //
+  // Handle events.
+  //
+
+  public void keyPressed(KeyEvent evt) {
+    processLocalKeyEvent(evt);
+  }
+  public void keyReleased(KeyEvent evt) {
+    processLocalKeyEvent(evt);
+  }
+  public void keyTyped(KeyEvent evt) {
+    evt.consume();
+  }
+
+  public void mousePressed(MouseEvent evt) {
+    processLocalMouseEvent(evt, false);
+  }
+  public void mouseReleased(MouseEvent evt) {
+    processLocalMouseEvent(evt, false);
+  }
+  public void mouseMoved(MouseEvent evt) {
+    processLocalMouseEvent(evt, true);
+  }
+  public void mouseDragged(MouseEvent evt) {
+    processLocalMouseEvent(evt, true);
+  }
+
+  public void processLocalKeyEvent(KeyEvent evt) {
+    if (viewer.rfb != null && rfb.inNormalProtocol) {
+      if (!inputEnabled) {
+	if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
+	    evt.getID() == KeyEvent.KEY_PRESSED ) {
+	  // Request screen update.
+	  try {
+	    rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+					      rfb.framebufferHeight, false);
+	  } catch (IOException e) {
+	    e.printStackTrace();
+	  }
+	}
+      } else {
+	// Input enabled.
+	synchronized(rfb) {
+	  try {
+	    rfb.writeKeyEvent(evt);
+	  } catch (Exception e) {
+	    e.printStackTrace();
+	  }
+	  rfb.notify();
+	}
+      }
+    }
+    // Don't ever pass keyboard events to AWT for default processing. 
+    // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
+    evt.consume();
+  }
+
+  public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
+    if (viewer.rfb != null && rfb.inNormalProtocol) {
+      if (moved) {
+	softCursorMove(evt.getX(), evt.getY());
+      }
+      if (rfb.framebufferWidth != scaledWidth) {
+        int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
+        int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
+        evt.translatePoint(sx - evt.getX(), sy - evt.getY());
+      }
+      synchronized(rfb) {
+	try {
+	  rfb.writePointerEvent(evt);
+	} catch (Exception e) {
+	  e.printStackTrace();
+	}
+	rfb.notify();
+      }
+    }
+  }
+
+  //
+  // Ignored events.
+  //
+
+  public void mouseClicked(MouseEvent evt) {}
+  public void mouseEntered(MouseEvent evt) {}
+  public void mouseExited(MouseEvent evt) {}
+
+  //
+  // Reset update statistics.
+  //
+
+  void resetStats() {
+    statStartTime = System.currentTimeMillis();
+    statNumUpdates = 0;
+    statNumTotalRects = 0;
+    statNumPixelRects = 0;
+    statNumRectsTight = 0;
+    statNumRectsTightJPEG = 0;
+    statNumRectsZRLE = 0;
+    statNumRectsHextile = 0;
+    statNumRectsRaw = 0;
+    statNumRectsCopy = 0;
+    statNumBytesEncoded = 0;
+    statNumBytesDecoded = 0;
+  }
+
+  //////////////////////////////////////////////////////////////////
+  //
+  // Handle cursor shape updates (XCursor and RichCursor encodings).
+  //
+
+  boolean showSoftCursor = false;
+
+  MemoryImageSource softCursorSource;
+  Image softCursor;
+
+  int cursorX = 0, cursorY = 0;
+  int cursorWidth, cursorHeight;
+  int origCursorWidth, origCursorHeight;
+  int hotX, hotY;
+  int origHotX, origHotY;
+
+  //
+  // Handle cursor shape update (XCursor and RichCursor encodings).
+  //
+
+  synchronized void
+    handleCursorShapeUpdate(int encodingType,
+			    int xhot, int yhot, int width, int height)
+    throws IOException {
+
+    softCursorFree();
+
+    if (width * height == 0)
+      return;
+
+    // Ignore cursor shape data if requested by user.
+    if (viewer.options.ignoreCursorUpdates) {
+      int bytesPerRow = (width + 7) / 8;
+      int bytesMaskData = bytesPerRow * height;
+
+      if (encodingType == rfb.EncodingXCursor) {
+	rfb.skipBytes(6 + bytesMaskData * 2);
+      } else {
+	// rfb.EncodingRichCursor
+	rfb.skipBytes(width * height * bytesPixel + bytesMaskData);
+      }
+      return;
+    }
+
+    // Decode cursor pixel data.
+    softCursorSource = decodeCursorShape(encodingType, width, height);
+
+    // Set original (non-scaled) cursor dimensions.
+    origCursorWidth = width;
+    origCursorHeight = height;
+    origHotX = xhot;
+    origHotY = yhot;
+
+    // Create off-screen cursor image.
+    createSoftCursor();
+
+    // Show the cursor.
+    showSoftCursor = true;
+    repaint(viewer.deferCursorUpdates,
+	    cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
+  }
+
+  //
+  // decodeCursorShape(). Decode cursor pixel data and return
+  // corresponding MemoryImageSource instance.
+  //
+
+  synchronized MemoryImageSource
+    decodeCursorShape(int encodingType, int width, int height)
+    throws IOException {
+
+    int bytesPerRow = (width + 7) / 8;
+    int bytesMaskData = bytesPerRow * height;
+
+    int[] softCursorPixels = new int[width * height];
+
+    if (encodingType == rfb.EncodingXCursor) {
+
+      // Read foreground and background colors of the cursor.
+      byte[] rgb = new byte[6];
+      rfb.readFully(rgb);
+      int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
+			(rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
+		       (0xFF000000 | (rgb[0] & 0xFF) << 16 |
+			(rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
+
+      // Read pixel and mask data.
+      byte[] pixBuf = new byte[bytesMaskData];
+      rfb.readFully(pixBuf);
+      byte[] maskBuf = new byte[bytesMaskData];
+      rfb.readFully(maskBuf);
+
+      // Decode pixel data into softCursorPixels[].
+      byte pixByte, maskByte;
+      int x, y, n, result;
+      int i = 0;
+      for (y = 0; y < height; y++) {
+	for (x = 0; x < width / 8; x++) {
+	  pixByte = pixBuf[y * bytesPerRow + x];
+	  maskByte = maskBuf[y * bytesPerRow + x];
+	  for (n = 7; n >= 0; n--) {
+	    if ((maskByte >> n & 1) != 0) {
+	      result = colors[pixByte >> n & 1];
+	    } else {
+	      result = 0;	// Transparent pixel
+	    }
+	    softCursorPixels[i++] = result;
+	  }
+	}
+	for (n = 7; n >= 8 - width % 8; n--) {
+	  if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
+	    result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
+	  } else {
+	    result = 0;		// Transparent pixel
+	  }
+	  softCursorPixels[i++] = result;
+	}
+      }
+
+    } else {
+      // encodingType == rfb.EncodingRichCursor
+
+      // Read pixel and mask data.
+      byte[] pixBuf = new byte[width * height * bytesPixel];
+      rfb.readFully(pixBuf);
+      byte[] maskBuf = new byte[bytesMaskData];
+      rfb.readFully(maskBuf);
+
+      // Decode pixel data into softCursorPixels[].
+      byte pixByte, maskByte;
+      int x, y, n, result;
+      int i = 0;
+      for (y = 0; y < height; y++) {
+	for (x = 0; x < width / 8; x++) {
+	  maskByte = maskBuf[y * bytesPerRow + x];
+	  for (n = 7; n >= 0; n--) {
+	    if ((maskByte >> n & 1) != 0) {
+	      if (bytesPixel == 1) {
+		result = cm8.getRGB(pixBuf[i]);
+	      } else {
+		result = 0xFF000000 |
+		  (pixBuf[i * 4 + 2] & 0xFF) << 16 |
+		  (pixBuf[i * 4 + 1] & 0xFF) << 8 |
+		  (pixBuf[i * 4] & 0xFF);
+	      }
+	    } else {
+	      result = 0;	// Transparent pixel
+	    }
+	    softCursorPixels[i++] = result;
+	  }
+	}
+	for (n = 7; n >= 8 - width % 8; n--) {
+	  if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
+	    if (bytesPixel == 1) {
+	      result = cm8.getRGB(pixBuf[i]);
+	    } else {
+	      result = 0xFF000000 |
+		(pixBuf[i * 4 + 2] & 0xFF) << 16 |
+		(pixBuf[i * 4 + 1] & 0xFF) << 8 |
+		(pixBuf[i * 4] & 0xFF);
+	    }
+	  } else {
+	    result = 0;		// Transparent pixel
+	  }
+	  softCursorPixels[i++] = result;
+	}
+      }
+
+    }
+
+    return new MemoryImageSource(width, height, softCursorPixels, 0, width);
+  }
+
+  //
+  // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
+  // Uses softCursorSource as a source for new cursor image.
+  //
+
+  synchronized void
+    createSoftCursor() {
+
+    if (softCursorSource == null)
+      return;
+
+    int scaleCursor = viewer.options.scaleCursor;
+    if (scaleCursor == 0 || !inputEnabled)
+      scaleCursor = 100;
+
+    // Save original cursor coordinates.
+    int x = cursorX - hotX;
+    int y = cursorY - hotY;
+    int w = cursorWidth;
+    int h = cursorHeight;
+
+    cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
+    cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
+    hotX = (origHotX * scaleCursor + 50) / 100;
+    hotY = (origHotY * scaleCursor + 50) / 100;
+    softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
+
+    if (scaleCursor != 100) {
+      softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
+						Image.SCALE_SMOOTH);
+    }
+
+    if (showSoftCursor) {
+      // Compute screen area to update.
+      x = Math.min(x, cursorX - hotX);
+      y = Math.min(y, cursorY - hotY);
+      w = Math.max(w, cursorWidth);
+      h = Math.max(h, cursorHeight);
+
+      repaint(viewer.deferCursorUpdates, x, y, w, h);
+    }
+  }
+
+  //
+  // softCursorMove(). Moves soft cursor into a particular location.
+  //
+
+  synchronized void softCursorMove(int x, int y) {
+    int oldX = cursorX;
+    int oldY = cursorY;
+    cursorX = x;
+    cursorY = y;
+    if (showSoftCursor) {
+      repaint(viewer.deferCursorUpdates,
+	      oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
+      repaint(viewer.deferCursorUpdates,
+	      cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
+    }
+  }
+
+  //
+  // softCursorFree(). Remove soft cursor, dispose resources.
+  //
+
+  synchronized void softCursorFree() {
+    if (showSoftCursor) {
+      showSoftCursor = false;
+      softCursor = null;
+      softCursorSource = null;
+
+      repaint(viewer.deferCursorUpdates,
+	      cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/VncCanvas2.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,63 @@
+//
+//  Copyright (C) 2006 Constantin Kaplinsky.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+import java.awt.*;
+import java.io.*;
+
+//
+// VncCanvas2 is a special version of VncCanvas which may use Java 2 API.
+//
+
+class VncCanvas2 extends VncCanvas {
+
+  public VncCanvas2(VncViewer v) throws IOException {
+    super(v);
+    disableFocusTraversalKeys();
+  }
+
+  public VncCanvas2(VncViewer v, int maxWidth_, int maxHeight_)
+    throws IOException {
+
+    super(v, maxWidth_, maxHeight_);
+    disableFocusTraversalKeys();
+  }
+
+  public void paintScaledFrameBuffer(Graphics g) {
+    Graphics2D g2d = (Graphics2D)g;
+    g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+                         RenderingHints.VALUE_RENDER_QUALITY);
+    g2d.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
+  }
+
+  //
+  // Try to disable focus traversal keys (JVMs 1.4 and higher).
+  //
+
+  private void disableFocusTraversalKeys() {
+    try {
+      Class[] argClasses = { Boolean.TYPE };
+      java.lang.reflect.Method method =
+        getClass().getMethod("setFocusTraversalKeysEnabled", argClasses);
+      Object[] argObjects = { new Boolean(false) };
+      method.invoke(this, argObjects);
+    } catch (Exception e) {}
+  }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/VncViewer.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,1019 @@
+//
+//  Copyright (C) 2001-2004 HorizonLive.com, Inc.  All Rights Reserved.
+//  Copyright (C) 2002 Constantin Kaplinsky.  All Rights Reserved.
+//  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+//
+//  This is free software; you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation; either version 2 of the License, or
+//  (at your option) any later version.
+//
+//  This software is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this software; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+//  USA.
+//
+
+//
+// VncViewer.java - the VNC viewer applet.  This class mainly just sets up the
+// user interface, leaving it to the VncCanvas to do the actual rendering of
+// a VNC desktop.
+//
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.*;
+
+public class VncViewer extends java.applet.Applet
+  implements java.lang.Runnable, WindowListener {
+
+  boolean inAnApplet = true;
+  boolean inSeparateFrame = false;
+
+  //
+  // main() is called when run as a java program from the command line.
+  // It simply runs the applet inside a newly-created frame.
+  //
+
+  public static void main(String[] argv) {
+    VncViewer v = new VncViewer();
+    v.mainArgs = argv;
+    v.inAnApplet = false;
+    v.inSeparateFrame = true;
+    /*
+    if(argv.length > 1){
+		v.host = argv[0];
+		v.port = Integer.parseInt(argv[1]);
+    }
+    */
+    v.init();
+    v.start();
+  }
+
+  String[] mainArgs;
+
+  RfbProto rfb;
+  Thread rfbThread;
+
+  Frame vncFrame;
+  Container vncContainer;
+  ScrollPane desktopScrollPane;
+  GridBagLayout gridbag;
+  ButtonPanel buttonPanel;
+  Label connStatusLabel;
+  VncCanvas vc;
+  OptionsFrame options;
+  ClipboardFrame clipboard;
+  RecordingFrame rec;
+
+  // Control session recording.
+  Object recordingSync;
+  String sessionFileName;
+  boolean recordingActive;
+  boolean recordingStatusChanged;
+  String cursorUpdatesDef;
+  String eightBitColorsDef;
+
+  // Variables read from parameter values.
+  String socketFactory;
+  String host;
+  int port;
+  String passwordParam;
+  boolean showControls;
+  boolean offerRelogin;
+  boolean showOfflineDesktop;
+  int deferScreenUpdates;
+  int deferCursorUpdates;
+  int deferUpdateRequests;
+  int debugStatsExcludeUpdates;
+  int debugStatsMeasureUpdates;
+
+  // Reference to this applet for inter-applet communication.
+  public static java.applet.Applet refApplet;
+
+  //
+  // init()
+  //
+
+  public void init() {
+
+    readParameters();
+
+    refApplet = this;
+
+    if (inSeparateFrame) {
+      vncFrame = new Frame("TightVNC");
+      if (!inAnApplet) {
+	vncFrame.add("Center", this);
+      }
+      vncContainer = vncFrame;
+    } else {
+      vncContainer = this;
+    }
+
+    recordingSync = new Object();
+
+    options = new OptionsFrame(this);
+    clipboard = new ClipboardFrame(this);
+    if (RecordingFrame.checkSecurity())
+      rec = new RecordingFrame(this);
+
+    sessionFileName = null;
+    recordingActive = false;
+    recordingStatusChanged = false;
+    cursorUpdatesDef = null;
+    eightBitColorsDef = null;
+
+    if (inSeparateFrame)
+      vncFrame.addWindowListener(this);
+
+    rfbThread = new Thread(this);
+    rfbThread.start();
+  }
+
+  public void update(Graphics g) {
+  }
+
+  //
+  // run() - executed by the rfbThread to deal with the RFB socket.
+  //
+
+  public void run() {
+
+    gridbag = new GridBagLayout();
+    vncContainer.setLayout(gridbag);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.anchor = GridBagConstraints.NORTHWEST;
+
+    if (showControls) {
+      buttonPanel = new ButtonPanel(this);
+      gridbag.setConstraints(buttonPanel, gbc);
+      vncContainer.add(buttonPanel);
+    }
+
+    try {
+      connectAndAuthenticate();
+      doProtocolInitialisation();
+
+      // FIXME: Use auto-scaling not only in a separate frame.
+      if (options.autoScale && inSeparateFrame) {
+	Dimension screenSize;
+	try {
+	  screenSize = vncContainer.getToolkit().getScreenSize();
+	} catch (Exception e) {
+	  screenSize = new Dimension(0, 0);
+	}
+	createCanvas(screenSize.width - 32, screenSize.height - 32);
+      } else {
+	createCanvas(0, 0);
+      }
+
+      gbc.weightx = 1.0;
+      gbc.weighty = 1.0;
+
+      if (inSeparateFrame) {
+
+	// Create a panel which itself is resizeable and can hold
+	// non-resizeable VncCanvas component at the top left corner.
+	Panel canvasPanel = new Panel();
+	canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+	canvasPanel.add(vc);
+
+	// Create a ScrollPane which will hold a panel with VncCanvas
+	// inside.
+	desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
+	gbc.fill = GridBagConstraints.BOTH;
+	gridbag.setConstraints(desktopScrollPane, gbc);
+	desktopScrollPane.add(canvasPanel);
+
+	// Finally, add our ScrollPane to the Frame window.
+	vncFrame.add(desktopScrollPane);
+	vncFrame.setTitle(rfb.desktopName);
+	vncFrame.pack();
+	vc.resizeDesktopFrame();
+
+      } else {
+
+	// Just add the VncCanvas component to the Applet.
+	gridbag.setConstraints(vc, gbc);
+	add(vc);
+	validate();
+
+      }
+
+      if (showControls)
+	buttonPanel.enableButtons();
+
+      moveFocusToDesktop();
+      processNormalProtocol();//main loop
+
+    } catch (NoRouteToHostException e) {
+      fatalError("Network error: no route to server: " + host, e);
+    } catch (UnknownHostException e) {
+      fatalError("Network error: server name unknown: " + host, e);
+    } catch (ConnectException e) {
+      fatalError("Network error: could not connect to server: " +
+		 host + ":" + port, e);
+    } catch (EOFException e) {
+      if (showOfflineDesktop) {
+	e.printStackTrace();
+	System.out.println("Network error: remote side closed connection");
+	if (vc != null) {
+	  vc.enableInput(false);
+	}
+	if (inSeparateFrame) {
+	  vncFrame.setTitle(rfb.desktopName + " [disconnected]");
+	}
+	if (rfb != null && !rfb.closed())
+	  rfb.close();
+	if (showControls && buttonPanel != null) {
+	  buttonPanel.disableButtonsOnDisconnect();
+	  if (inSeparateFrame) {
+	    vncFrame.pack();
+	  } else {
+	    validate();
+	  }
+	}
+      } else {
+	fatalError("Network error: remote side closed connection", e);
+      }
+    } catch (IOException e) {
+      String str = e.getMessage();
+      if (str != null && str.length() != 0) {
+	fatalError("Network Error: " + str, e);
+      } else {
+	fatalError(e.toString(), e);
+      }
+    } catch (Exception e) {
+      String str = e.getMessage();
+      if (str != null && str.length() != 0) {
+	fatalError("Error: " + str, e);
+      } else {
+	fatalError(e.toString(), e);
+      }
+    }
+    
+  }
+
+  //
+  // Create a VncCanvas instance.
+  //
+
+  void createCanvas(int maxWidth, int maxHeight) throws IOException {
+    // Determine if Java 2D API is available and use a special
+    // version of VncCanvas if it is present.
+    vc = null;
+    try {
+      // This throws ClassNotFoundException if there is no Java 2D API.
+      Class cl = Class.forName("java.awt.Graphics2D");
+      // If we could load Graphics2D class, then we can use VncCanvas2D.
+      cl = Class.forName("VncCanvas2");
+      Class[] argClasses = { this.getClass(), Integer.TYPE, Integer.TYPE };
+      java.lang.reflect.Constructor cstr = cl.getConstructor(argClasses);
+      Object[] argObjects =
+        { this, new Integer(maxWidth), new Integer(maxHeight) };
+      vc = (VncCanvas)cstr.newInstance(argObjects);
+    } catch (Exception e) {
+      System.out.println("Warning: Java 2D API is not available");
+    }
+
+    // If we failed to create VncCanvas2D, use old VncCanvas.
+    if (vc == null)
+      vc = new VncCanvas(this, maxWidth, maxHeight);
+  }
+
+
+  //
+  // Process RFB socket messages.
+  // If the rfbThread is being stopped, ignore any exceptions,
+  // otherwise rethrow the exception so it can be handled.
+  //
+ 
+  void processNormalProtocol() throws Exception {
+    try {
+      vc.processNormalProtocol();//main loop
+    } catch (Exception e) {
+      if (rfbThread == null) {
+	System.out.println("Ignoring RFB socket exceptions" +
+			   " because applet is stopping");
+      } else {
+	throw e;
+      }
+    }
+  }
+
+
+  //
+  // Connect to the RFB server and authenticate the user.
+  //
+
+  void connectAndAuthenticate() throws Exception
+  {
+    showConnectionStatus("Initializing...");
+    if (inSeparateFrame) {
+      vncFrame.pack();
+      vncFrame.show();
+    } else {
+      validate();
+    }
+
+    showConnectionStatus("Connecting to " + host + ", port " + port + "...");
+
+    rfb = new RfbProto(host, port, this);
+    showConnectionStatus("Connected to server");
+
+    rfb.readVersionMsg();
+    showConnectionStatus("RFB server supports protocol version " +
+			 rfb.serverMajor + "." + rfb.serverMinor);
+
+    rfb.writeVersionMsg();
+    showConnectionStatus("Using RFB protocol version " +
+			 rfb.clientMajor + "." + rfb.clientMinor);
+
+    int secType = rfb.negotiateSecurity();
+    int authType;
+    if (secType == RfbProto.SecTypeTight) {
+      showConnectionStatus("Enabling TightVNC protocol extensions");
+      rfb.setupTunneling();
+      authType = rfb.negotiateAuthenticationTight();
+    } else {
+      authType = secType;
+    }
+
+    switch (authType) {
+    case RfbProto.AuthNone:
+      showConnectionStatus("No authentication needed");
+      rfb.authenticateNone();
+      break;
+    case RfbProto.AuthVNC:
+      showConnectionStatus("Performing standard VNC authentication");
+      if (passwordParam != null) {
+        rfb.authenticateVNC(passwordParam);
+      } else {
+        String pw = askPassword();
+        rfb.authenticateVNC(pw);
+      }
+      break;
+    default:
+      throw new Exception("Unknown authentication scheme " + authType);
+    }
+  }
+
+
+  //
+  // Show a message describing the connection status.
+  // To hide the connection status label, use (msg == null).
+  //
+
+  void showConnectionStatus(String msg)
+  {
+    if (msg == null) {
+      if (vncContainer.isAncestorOf(connStatusLabel)) {
+	vncContainer.remove(connStatusLabel);
+      }
+      return;
+    }
+
+    System.out.println(msg);
+
+    if (connStatusLabel == null) {
+      connStatusLabel = new Label("Status: " + msg);
+      connStatusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+    } else {
+      connStatusLabel.setText("Status: " + msg);
+    }
+
+    if (!vncContainer.isAncestorOf(connStatusLabel)) {
+      GridBagConstraints gbc = new GridBagConstraints();
+      gbc.gridwidth = GridBagConstraints.REMAINDER;
+      gbc.fill = GridBagConstraints.HORIZONTAL;
+      gbc.anchor = GridBagConstraints.NORTHWEST;
+      gbc.weightx = 1.0;
+      gbc.weighty = 1.0;
+      gbc.insets = new Insets(20, 30, 20, 30);
+      gridbag.setConstraints(connStatusLabel, gbc);
+      vncContainer.add(connStatusLabel);
+    }
+
+    if (inSeparateFrame) {
+      vncFrame.pack();
+    } else {
+      validate();
+    }
+  }
+
+
+  //
+  // Show an authentication panel.
+  //
+
+  String askPassword() throws Exception
+  {
+    showConnectionStatus(null);
+
+    AuthPanel authPanel = new AuthPanel(this);
+
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridwidth = GridBagConstraints.REMAINDER;
+    gbc.anchor = GridBagConstraints.NORTHWEST;
+    gbc.weightx = 1.0;
+    gbc.weighty = 1.0;
+    gbc.ipadx = 100;
+    gbc.ipady = 50;
+    gridbag.setConstraints(authPanel, gbc);
+    vncContainer.add(authPanel);
+
+    if (inSeparateFrame) {
+      vncFrame.pack();
+    } else {
+      validate();
+    }
+
+    authPanel.moveFocusToDefaultField();
+    String pw = authPanel.getPassword();
+    vncContainer.remove(authPanel);
+
+    return pw;
+  }
+
+
+  //
+  // Do the rest of the protocol initialisation.
+  //
+
+  void doProtocolInitialisation() throws IOException
+  {
+    rfb.writeClientInit();
+    rfb.readServerInit();
+
+    System.out.println("Desktop name is " + rfb.desktopName);
+    System.out.println("Desktop size is " + rfb.framebufferWidth + " x " +
+		       rfb.framebufferHeight);
+
+    setEncodings();
+
+    showConnectionStatus(null);
+  }
+
+
+  //
+  // Send current encoding list to the RFB server.
+  //
+
+  int[] encodingsSaved;
+  int nEncodingsSaved;
+
+  void setEncodings()        { setEncodings(false); }
+  void autoSelectEncodings() { setEncodings(true); }
+
+  void setEncodings(boolean autoSelectOnly) {
+    if (options == null || rfb == null || !rfb.inNormalProtocol)
+      return;
+
+    int preferredEncoding = options.preferredEncoding;
+    if (preferredEncoding == -1) {
+      long kbitsPerSecond = rfb.kbitsPerSecond();
+      if (nEncodingsSaved < 1) {
+        // Choose Tight or ZRLE encoding for the very first update.
+        System.out.println("Using Tight/ZRLE encodings");
+        preferredEncoding = RfbProto.EncodingTight;
+      } else if (kbitsPerSecond > 2000 &&
+                 encodingsSaved[0] != RfbProto.EncodingHextile) {
+        // Switch to Hextile if the connection speed is above 2Mbps.
+        System.out.println("Throughput " + kbitsPerSecond +
+                           " kbit/s - changing to Hextile encoding");
+        preferredEncoding = RfbProto.EncodingHextile;
+      } else if (kbitsPerSecond < 1000 &&
+                 encodingsSaved[0] != RfbProto.EncodingTight) {
+        // Switch to Tight/ZRLE if the connection speed is below 1Mbps.
+        System.out.println("Throughput " + kbitsPerSecond +
+                           " kbit/s - changing to Tight/ZRLE encodings");
+        preferredEncoding = RfbProto.EncodingTight;
+      } else {
+        // Don't change the encoder.
+        if (autoSelectOnly)
+          return;
+        preferredEncoding = encodingsSaved[0];
+      }
+    } else {
+      // Auto encoder selection is not enabled.
+      if (autoSelectOnly)
+        return;
+    }
+
+    int[] encodings = new int[20];
+    int nEncodings = 0;
+
+    encodings[nEncodings++] = preferredEncoding;
+    if (options.useCopyRect) {
+      encodings[nEncodings++] = RfbProto.EncodingCopyRect;
+    }
+
+    if (preferredEncoding != RfbProto.EncodingTight) {
+      encodings[nEncodings++] = RfbProto.EncodingTight;
+    }
+    if (preferredEncoding != RfbProto.EncodingZRLE) {
+      encodings[nEncodings++] = RfbProto.EncodingZRLE;
+    }
+    if (preferredEncoding != RfbProto.EncodingHextile) {
+      encodings[nEncodings++] = RfbProto.EncodingHextile;
+    }
+    if (preferredEncoding != RfbProto.EncodingZlib) {
+      encodings[nEncodings++] = RfbProto.EncodingZlib;
+    }
+    if (preferredEncoding != RfbProto.EncodingCoRRE) {
+      encodings[nEncodings++] = RfbProto.EncodingCoRRE;
+    }
+    if (preferredEncoding != RfbProto.EncodingRRE) {
+      encodings[nEncodings++] = RfbProto.EncodingRRE;
+    }
+
+    if (options.compressLevel >= 0 && options.compressLevel <= 9) {
+      encodings[nEncodings++] =
+        RfbProto.EncodingCompressLevel0 + options.compressLevel;
+    }
+    if (options.jpegQuality >= 0 && options.jpegQuality <= 9) {
+      encodings[nEncodings++] =
+        RfbProto.EncodingQualityLevel0 + options.jpegQuality;
+    }
+
+    if (options.requestCursorUpdates) {
+      encodings[nEncodings++] = RfbProto.EncodingXCursor;
+      encodings[nEncodings++] = RfbProto.EncodingRichCursor;
+      if (!options.ignoreCursorUpdates)
+	encodings[nEncodings++] = RfbProto.EncodingPointerPos;
+    }
+
+    encodings[nEncodings++] = RfbProto.EncodingLastRect;
+    encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
+
+    boolean encodingsWereChanged = false;
+    if (nEncodings != nEncodingsSaved) {
+      encodingsWereChanged = true;
+    } else {
+      for (int i = 0; i < nEncodings; i++) {
+        if (encodings[i] != encodingsSaved[i]) {
+          encodingsWereChanged = true;
+          break;
+        }
+      }
+    }
+
+    if (encodingsWereChanged) {
+      try {
+        rfb.writeSetEncodings(encodings, nEncodings);
+        if (vc != null) {
+          vc.softCursorFree();
+        }
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+      encodingsSaved = encodings;
+      nEncodingsSaved = nEncodings;
+    }
+  }
+
+
+  //
+  // setCutText() - send the given cut text to the RFB server.
+  //
+
+  void setCutText(String text) {
+    try {
+      if (rfb != null && rfb.inNormalProtocol) {
+	rfb.writeClientCutText(text);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+
+  //
+  // Order change in session recording status. To stop recording, pass
+  // null in place of the fname argument.
+  //
+
+  void setRecordingStatus(String fname) {
+    synchronized(recordingSync) {
+      sessionFileName = fname;
+      recordingStatusChanged = true;
+    }
+  }
+
+  //
+  // Start or stop session recording. Returns true if this method call
+  // causes recording of a new session.
+  //
+
+  boolean checkRecordingStatus() throws IOException {
+    synchronized(recordingSync) {
+      if (recordingStatusChanged) {
+	recordingStatusChanged = false;
+	if (sessionFileName != null) {
+	  startRecording();
+	  return true;
+	} else {
+	  stopRecording();
+	}
+      }
+    }
+    return false;
+  }
+
+  //
+  // Start session recording.
+  //
+
+  protected void startRecording() throws IOException {
+    synchronized(recordingSync) {
+      if (!recordingActive) {
+	// Save settings to restore them after recording the session.
+	cursorUpdatesDef =
+	  options.choices[options.cursorUpdatesIndex].getSelectedItem();
+	eightBitColorsDef =
+	  options.choices[options.eightBitColorsIndex].getSelectedItem();
+	// Set options to values suitable for recording.
+	options.choices[options.cursorUpdatesIndex].select("Disable");
+	options.choices[options.cursorUpdatesIndex].setEnabled(false);
+	options.setEncodings();
+	options.choices[options.eightBitColorsIndex].select("No");
+	options.choices[options.eightBitColorsIndex].setEnabled(false);
+	options.setColorFormat();
+      } else {
+	rfb.closeSession();
+      }
+
+      System.out.println("Recording the session in " + sessionFileName);
+      rfb.startSession(sessionFileName);
+      recordingActive = true;
+    }
+  }
+
+  //
+  // Stop session recording.
+  //
+
+  protected void stopRecording() throws IOException {
+    synchronized(recordingSync) {
+      if (recordingActive) {
+	// Restore options.
+	options.choices[options.cursorUpdatesIndex].select(cursorUpdatesDef);
+	options.choices[options.cursorUpdatesIndex].setEnabled(true);
+	options.setEncodings();
+	options.choices[options.eightBitColorsIndex].select(eightBitColorsDef);
+	options.choices[options.eightBitColorsIndex].setEnabled(true);
+	options.setColorFormat();
+
+	rfb.closeSession();
+	System.out.println("Session recording stopped.");
+      }
+      sessionFileName = null;
+      recordingActive = false;
+    }
+  }
+
+
+  //
+  // readParameters() - read parameters from the html source or from the
+  // command line.  On the command line, the arguments are just a sequence of
+  // param_name/param_value pairs where the names and values correspond to
+  // those expected in the html applet tag source.
+  //
+
+  void readParameters() {
+      //      host = readParameter("HOST", !inAnApplet);
+      if(mainArgs.length > 0) host = mainArgs[0];
+      else host = "hades.cr.ie.u-ryukyu.ac.jp";
+	  /*	  
+    if (host == null) {
+      host = getCodeBase().getHost();
+      if (host.equals("")) {
+      fatalError("HOST parameter not specified");
+	}
+      }
+	  */
+
+	  //    port = readIntParameter("PORT", 5900);
+      if(mainArgs.length > 1) port = Integer.parseInt(mainArgs[1]);
+      else  port = 5900;
+    // Read "ENCPASSWORD" or "PASSWORD" parameter if specified.
+    readPasswordParameters();
+
+    String str;
+    if (inAnApplet) {
+      str = readParameter("Open New Window", false);
+      if (str != null && str.equalsIgnoreCase("Yes"))
+	inSeparateFrame = true;
+    }
+
+    // "Show Controls" set to "No" disables button panel.
+    showControls = true;
+    str = readParameter("Show Controls", false);
+    if (str != null && str.equalsIgnoreCase("No"))
+      showControls = false;
+
+    // "Offer Relogin" set to "No" disables "Login again" and "Close
+    // window" buttons under error messages in applet mode.
+    offerRelogin = true;
+    str = readParameter("Offer Relogin", false);
+    if (str != null && str.equalsIgnoreCase("No"))
+      offerRelogin = false;
+
+    // Do we continue showing desktop on remote disconnect?
+    showOfflineDesktop = false;
+    str = readParameter("Show Offline Desktop", false);
+    if (str != null && str.equalsIgnoreCase("Yes"))
+      showOfflineDesktop = true;
+
+    // Fine tuning options.
+    deferScreenUpdates = readIntParameter("Defer screen updates", 20);
+    deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
+    deferUpdateRequests = readIntParameter("Defer update requests", 0);
+
+    // Debugging options.
+    debugStatsExcludeUpdates = readIntParameter("DEBUG_XU", 0);
+    debugStatsMeasureUpdates = readIntParameter("DEBUG_CU", 0);
+
+    // SocketFactory.
+    socketFactory = readParameter("SocketFactory", false);
+  }
+
+  //
+  // Read password parameters. If an "ENCPASSWORD" parameter is set,
+  // then decrypt the password into the passwordParam string. Otherwise,
+  // try to read the "PASSWORD" parameter directly to passwordParam.
+  //
+
+  private void readPasswordParameters() {
+    String encPasswordParam = readParameter("ENCPASSWORD", false);
+    if (encPasswordParam == null) {
+      passwordParam = readParameter("PASSWORD", false);
+
+    } else {
+      // ENCPASSWORD is hexascii-encoded. Decode.
+      byte[] pw = {0, 0, 0, 0, 0, 0, 0, 0};
+      int len = encPasswordParam.length() / 2;
+      if (len > 8)
+        len = 8;
+      for (int i = 0; i < len; i++) {
+        String hex = encPasswordParam.substring(i*2, i*2+2);
+        Integer x = new Integer(Integer.parseInt(hex, 16));
+        pw[i] = x.byteValue();
+      }
+      // Decrypt the password.
+      byte[] key = {23, 82, 107, 6, 35, 78, 88, 7};
+      DesCipher des = new DesCipher(key);
+      des.decrypt(pw, 0, pw, 0);
+      passwordParam = new String(pw);
+
+    }
+  }
+
+  public String readParameter(String name, boolean required) {
+    if (inAnApplet) {
+      String s = getParameter(name);
+      if ((s == null) && required) {
+	fatalError(name + " parameter not specified");
+      }
+      return s;
+    }
+
+    for (int i = 0; i < mainArgs.length; i += 2) {
+      if (mainArgs[i].equalsIgnoreCase(name)) {
+	try {
+	  return mainArgs[i+1];
+	} catch (Exception e) {
+	  if (required) {
+	    fatalError(name + " parameter not specified");
+	  }
+	  return null;
+	}
+      }
+    }
+    if (required) {
+      fatalError(name + " parameter not specified");
+    }
+    return null;
+  }
+
+  int readIntParameter(String name, int defaultValue) {
+    String str = readParameter(name, false);
+    int result = defaultValue;
+    if (str != null) {
+      try {
+	result = Integer.parseInt(str);
+      } catch (NumberFormatException e) { }
+    }
+    return result;
+  }
+
+  //
+  // moveFocusToDesktop() - move keyboard focus either to VncCanvas.
+  //
+
+  void moveFocusToDesktop() {
+    if (vncContainer != null) {
+      if (vc != null && vncContainer.isAncestorOf(vc))
+	vc.requestFocus();
+    }
+  }
+
+  //
+  // disconnect() - close connection to server.
+  //
+
+  synchronized public void disconnect() {
+    System.out.println("Disconnecting");
+
+    if (vc != null) {
+      double sec = (System.currentTimeMillis() - vc.statStartTime) / 1000.0;
+      double rate = Math.round(vc.statNumUpdates / sec * 100) / 100.0;
+      int nRealRects = vc.statNumPixelRects;
+      int nPseudoRects = vc.statNumTotalRects - vc.statNumPixelRects;
+      System.out.println("Updates received: " + vc.statNumUpdates + " (" +
+                         nRealRects + " rectangles + " + nPseudoRects +
+                         " pseudo), " + rate + " updates/sec");
+      int numRectsOther = nRealRects - vc.statNumRectsTight
+        - vc.statNumRectsZRLE - vc.statNumRectsHextile
+        - vc.statNumRectsRaw - vc.statNumRectsCopy;
+      System.out.println("Rectangles:" +
+                         " Tight=" + vc.statNumRectsTight +
+                         "(JPEG=" + vc.statNumRectsTightJPEG +
+                         ") ZRLE=" + vc.statNumRectsZRLE +
+                         " Hextile=" + vc.statNumRectsHextile +
+                         " Raw=" + vc.statNumRectsRaw +
+                         " CopyRect=" + vc.statNumRectsCopy +
+                         " other=" + numRectsOther);
+
+      int raw = vc.statNumBytesDecoded;
+      int compressed = vc.statNumBytesEncoded;
+      if (compressed > 0) {
+          double ratio = Math.round((double)raw / compressed * 1000) / 1000.0;
+          System.out.println("Pixel data: " + vc.statNumBytesDecoded +
+                             " bytes, " + vc.statNumBytesEncoded +
+                             " compressed, ratio " + ratio);
+      }
+    }
+
+    if (rfb != null && !rfb.closed())
+      rfb.close();
+    options.dispose();
+    clipboard.dispose();
+    if (rec != null)
+      rec.dispose();
+
+    if (inAnApplet) {
+      showMessage("Disconnected");
+    } else {
+      System.exit(0);
+    }
+  }
+
+  //
+  // fatalError() - print out a fatal error message.
+  // FIXME: Do we really need two versions of the fatalError() method?
+  //
+
+  synchronized public void fatalError(String str) {
+    System.out.println(str);
+
+    if (inAnApplet) {
+      // vncContainer null, applet not inited,
+      // can not present the error to the user.
+      Thread.currentThread().stop();
+    } else {
+      System.exit(1);
+    }
+  }
+
+  synchronized public void fatalError(String str, Exception e) {
+ 
+    if (rfb != null && rfb.closed()) {
+      // Not necessary to show error message if the error was caused
+      // by I/O problems after the rfb.close() method call.
+      System.out.println("RFB thread finished");
+      return;
+    }
+
+    System.out.println(str);
+    e.printStackTrace();
+
+    if (rfb != null)
+      rfb.close();
+
+    if (inAnApplet) {
+      showMessage(str);
+    } else {
+      System.exit(1);
+    }
+  }
+
+  //
+  // Show message text and optionally "Relogin" and "Close" buttons.
+  //
+
+  void showMessage(String msg) {
+    vncContainer.removeAll();
+
+    Label errLabel = new Label(msg, Label.CENTER);
+    errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+
+    if (offerRelogin) {
+
+      Panel gridPanel = new Panel(new GridLayout(0, 1));
+      Panel outerPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
+      outerPanel.add(gridPanel);
+      vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 16));
+      vncContainer.add(outerPanel);
+      Panel textPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
+      textPanel.add(errLabel);
+      gridPanel.add(textPanel);
+      gridPanel.add(new ReloginPanel(this));
+
+    } else {
+
+      vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
+      vncContainer.add(errLabel);
+
+    }
+
+    if (inSeparateFrame) {
+      vncFrame.pack();
+    } else {
+      validate();
+    }
+  }
+
+  //
+  // Stop the applet.
+  // Main applet thread will terminate on first exception
+  // after seeing that rfbThread has been set to null.
+  //
+
+  public void stop() {
+    System.out.println("Stopping applet");
+    rfbThread = null;
+  }
+
+  //
+  // This method is called before the applet is destroyed.
+  //
+
+  public void destroy() {
+    System.out.println("Destroying applet");
+
+    vncContainer.removeAll();
+    options.dispose();
+    clipboard.dispose();
+    if (rec != null)
+      rec.dispose();
+    if (rfb != null && !rfb.closed())
+      rfb.close();
+    if (inSeparateFrame)
+      vncFrame.dispose();
+  }
+
+  //
+  // Start/stop receiving mouse events.
+  //
+
+  public void enableInput(boolean enable) {
+    vc.enableInput(enable);
+  }
+
+  //
+  // Close application properly on window close event.
+  //
+
+  public void windowClosing(WindowEvent evt) {
+    System.out.println("Closing window");
+    if (rfb != null)
+      disconnect();
+
+    vncContainer.hide();
+
+    if (!inAnApplet) {
+      System.exit(0);
+    }
+  }
+
+  //
+  // Ignore window events we're not interested in.
+  //
+
+  public void windowActivated(WindowEvent evt) {}
+  public void windowDeactivated (WindowEvent evt) {}
+  public void windowOpened(WindowEvent evt) {}
+  public void windowClosed(WindowEvent evt) {}
+  public void windowIconified(WindowEvent evt) {}
+  public void windowDeiconified(WindowEvent evt) {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ZlibInStream.java	Tue Apr 12 12:58:05 2011 +0900
@@ -0,0 +1,111 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// A ZlibInStream reads from a zlib.io.InputStream
+//
+
+public class ZlibInStream extends InStream {
+
+  static final int defaultBufSize = 16384;
+
+  public ZlibInStream(int bufSize_) {
+    bufSize = bufSize_;
+    b = new byte[bufSize];
+    ptr = end = ptrOffset = 0;
+    inflater = new java.util.zip.Inflater();
+  }
+
+  public ZlibInStream() { this(defaultBufSize); }
+
+  public void setUnderlying(InStream is, int bytesIn_) {
+    underlying = is;
+    bytesIn = bytesIn_;
+    ptr = end = 0;
+  }
+
+  public void reset() throws Exception {
+    ptr = end = 0;
+    if (underlying == null) return;
+
+    while (bytesIn > 0) {
+      decompress();
+      end = 0; // throw away any data
+    }
+    underlying = null;
+  }
+
+  public int pos() { return ptrOffset + ptr; }
+
+  protected int overrun(int itemSize, int nItems) throws Exception {
+    if (itemSize > bufSize)
+      throw new Exception("ZlibInStream overrun: max itemSize exceeded");
+    if (underlying == null)
+      throw new Exception("ZlibInStream overrun: no underlying stream");
+
+    if (end - ptr != 0)
+      System.arraycopy(b, ptr, b, 0, end - ptr);
+
+    ptrOffset += ptr;
+    end -= ptr;
+    ptr = 0;
+
+    while (end < itemSize) {
+      decompress();
+    }
+
+    if (itemSize * nItems > end)
+      nItems = end / itemSize;
+
+    return nItems;
+  }
+
+  // decompress() calls the decompressor once.  Note that this won't
+  // necessarily generate any output data - it may just consume some input
+  // data.  Returns false if wait is false and we would block on the underlying
+  // stream.
+
+  private void decompress() throws Exception {
+    try {
+      underlying.check(1);
+      int avail_in = underlying.getend() - underlying.getptr();
+      if (avail_in > bytesIn)
+        avail_in = bytesIn;
+
+      if (inflater.needsInput()) {
+        inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in);
+      }
+
+      int n = inflater.inflate(b, end, bufSize - end); 
+
+      end += n;
+      if (inflater.needsInput()) {
+        bytesIn -= avail_in;
+        underlying.setptr(underlying.getptr() + avail_in);
+      }
+    } catch (java.util.zip.DataFormatException e) {
+      throw new Exception("ZlibInStream: inflate failed");
+    }
+  }
+
+  private InStream underlying;
+  private int bufSize;
+  private int ptrOffset;
+  private java.util.zip.Inflater inflater;
+  private int bytesIn;
+}