changeset 4:60a87e277536

indent
author e085711
date Wed, 13 Apr 2011 08:00:53 +0900
parents c930d146670f
children bdb8d7c7d4d1
files src/InStream.java src/RfbProto.java src/VncCanvas.java src/VncViewer.java src/ZlibInStream.java
diffstat 5 files changed, 3985 insertions(+), 3990 deletions(-) [+]
line wrap: on
line diff
--- a/src/InStream.java	Wed Apr 13 07:48:49 2011 +0900
+++ b/src/InStream.java	Wed Apr 13 08:00:53 2011 +0900
@@ -23,155 +23,178 @@
 
 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).
+	// 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);
 
-  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;
+	}
 
-      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 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 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 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 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 readU8() throws Exception {
-    return readS8() & 0xff;
-  }
+	public final int readU32() throws Exception {
+		return readS32() & 0xffffffff;
+	}
 
-  public final int readU16() throws Exception {
-    return readS16() & 0xffff;
-  }
+	// readString() reads a string - a U32 length followed by the data.
 
-  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");
 
-  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);
+	}
 
-    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++];
-      }
-    }
+	// maxStringLength protects against allocating a huge buffer. Set it
+	// higher if you need longer strings.
+
+	public static int maxStringLength = 65535;
 
-    return new String(str);
-  }
+	public final void skip(int bytes) throws Exception {
+		while (bytes > 0) {
+			int n = check(1, bytes);
+			ptr += n;
+			bytes -= n;
+		}
+	}
 
-  // maxStringLength protects against allocating a huge buffer.  Set it
-  // higher if you need longer strings.
-
-  public static int maxStringLength = 65535;
+	// readBytes() reads an exact number of bytes into an array at an offset.
 
-  public final void skip(int bytes) throws Exception {
-    while (bytes > 0) {
-      int n = check(1, bytes);
-      ptr += n;
-      bytes -= n;
-    }
-  }
+	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;
+		}
+	}
 
-  // readBytes() reads an exact number of bytes into an array at an offset.
+	// 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 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;
-    }
-  }
+	public final int readOpaque32() throws Exception {
+		return readU32();
+	}
 
-  // 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 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 readOpaque16() throws Exception {
-    return readU16();
-  }
+	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;
+	}
 
-  public final int readOpaque32() throws Exception {
-    return readU32();
-  }
+	// pos() returns the position in the stream.
 
-  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;
-  }
+	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;
+	}
 
-  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.
+	// 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.
 
-  abstract public int pos();
+	public final byte[] getbuf() {
+		return b;
+	}
 
-  // 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; }
+	public final int getptr() {
+		return 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 int getend() {
+		return end;
+	}
 
-  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; }
+	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).
+	// 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;
 
-  abstract protected int overrun(int itemSize, int nItems) throws Exception;
+	protected InStream() {
+	}
 
-  protected InStream() {}
-  protected byte[] b;
-  protected int ptr;
-  protected int end;
+	protected byte[] b;
+	protected int ptr;
+	protected int end;
 }
--- a/src/RfbProto.java	Wed Apr 13 07:48:49 2011 +0900
+++ b/src/RfbProto.java	Wed Apr 13 08:00:53 2011 +0900
@@ -32,1349 +32,1326 @@
 
 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";
+	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";
 
-  // 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";
 
-  // Security types
-  final static int
-    SecTypeInvalid = 0,
-    SecTypeNone    = 1,
-    SecTypeVncAuth = 2,
-    SecTypeTight   = 16;
+	// 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;
 
-  // Supported tunneling types
-  final static int
-    NoTunneling = 0;
-  final static String
-    SigNoTunneling = "NOTUNNEL";
+	// Standard server-to-client messages
+	final static int FramebufferUpdate = 0, SetColourMapEntries = 1, Bell = 2,
+			ServerCutText = 3;
 
-  // Supported authentication types
-  final static int
-    AuthNone      = 1,
-    AuthVNC       = 2,
-    AuthUnixLogin = 129;
-  final static String
-    SigAuthNone      = "NOAUTH__",
-    SigAuthVNC       = "VNCAUTH_",
-    SigAuthUnixLogin = "ULGNAUTH";
+	// Non-standard server-to-client messages
+	final static int EndOfContinuousUpdates = 150;
+	final static String SigEndOfContinuousUpdates = "CUS_EOCU";
 
-  // VNC authentication results
-  final static int
-    VncAuthOK      = 0,
-    VncAuthFailed  = 1,
-    VncAuthTooMany = 2;
+	// Standard client-to-server messages
+	final static int SetPixelFormat = 0, FixColourMapEntries = 1,
+			SetEncodings = 2, FramebufferUpdateRequest = 3, KeyboardEvent = 4,
+			PointerEvent = 5, ClientCutText = 6;
 
-  // Standard server-to-client messages
-  final static int
-    FramebufferUpdate   = 0,
-    SetColourMapEntries = 1,
-    Bell                = 2,
-    ServerCutText       = 3;
+	// Non-standard client-to-server messages
+	final static int EnableContinuousUpdates = 150;
+	final static String SigEnableContinuousUpdates = "CUC_ENCU";
 
-  // Non-standard server-to-client messages
-  final static int
-    EndOfContinuousUpdates = 150;
-  final static String
-    SigEndOfContinuousUpdates = "CUS_EOCU";
+	// 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";
 
-  // Standard client-to-server messages
-  final static int
-    SetPixelFormat           = 0,
-    FixColourMapEntries      = 1,
-    SetEncodings             = 2,
-    FramebufferUpdateRequest = 3,
-    KeyboardEvent            = 4,
-    PointerEvent             = 5,
-    ClientCutText            = 6;
+	final static int MaxNormalEncoding = 255;
 
-  // Non-standard client-to-server messages
-  final static int
-    EnableContinuousUpdates = 150;
-  final static String
-    SigEnableContinuousUpdates = "CUC_ENCU";
+	// Contstants used in the Hextile decoder
+	final static int HextileRaw = 1, HextileBackgroundSpecified = 2,
+			HextileForegroundSpecified = 4, HextileAnySubrects = 8,
+			HextileSubrectsColoured = 16;
 
-  // 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";
+	// 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;
 
-  final static int MaxNormalEncoding = 255;
+	// 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;
+	}
 
-  // Contstants used in the Hextile decoder
-  final static int
-    HextileRaw                 = 1,
-    HextileBackgroundSpecified = 2,
-    HextileForegroundSpecified = 4,
-    HextileAnySubrects         = 8,
-    HextileSubrectsColoured    = 16;
+	// 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;
 
-  // 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;
+	// 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;
 
-  String host;
-  int port;
-  Socket sock;
-  OutputStream os;
-  SessionRecorder rec;
-  boolean inNormalProtocol = false;
-  VncViewer viewer;
+	// Measuring network throughput.
+	boolean timing;
+	long timeWaitedIn100us;
+	long timedKbits;
 
-  // 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; }
-  
-  
+	// 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;
 
-  // 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;
+	//
+	// Constructor. Make TCP connection to RFB server.
+	//
 
-  // 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;
+	RfbProto(String h, int p, VncViewer v) throws IOException {
+		viewer = v;
+		host = h;
+		port = p;
 
-  // This fields are needed to show warnings about inefficiently saved
-  // sessions only once per each saved session file.
-  boolean zlibWarningShown;
-  boolean tightWarningShown;
+		if (viewer.socketFactory == null) {
+			sock = new Socket(host, port);
 
-  // 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;
+		} 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();
 
-  // Protocol version and TightVNC-specific protocol options.
-  int serverMajor, serverMinor;
-  int clientMajor, clientMinor;
-  boolean protocolTightVNC;
-  CapsContainer tunnelCaps, authCaps;
-  CapsContainer serverMsgCaps, clientMsgCaps;
-  CapsContainer encodingCaps;
+		timing = false;
+		timeWaitedIn100us = 5;
+		timedKbits = 0;
+	}
 
-  // If true, informs that the RFB socket was closed.
-  private boolean closed;
+	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();
+		}
+	}
 
-  //
-  // Constructor. Make TCP connection to RFB server.
-  //
-
-  RfbProto(String h, int p, VncViewer v) throws IOException {
-    viewer = v;
-    host = h;
-    port = p;
+	synchronized boolean closed() {
+		return closed;
+	}
 
-    if (viewer.socketFactory == null) {
-      sock = new Socket(host, port);
-      
-    } 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();
+	//
+	// Read server's protocol version message
+	//
+
+	void readVersionMsg() throws Exception {
+
+		byte[] b = new byte[12];
+
+		readFully(b);
 
-    timing = false;
-    timeWaitedIn100us = 5;
-    timedKbits = 0;
-  }
+		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');
 
-  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();
-    }
-  }
+		if (serverMajor < 3) {
+			throw new Exception(
+					"RFB server does not support protocol version 3");
+		}
+	}
+
+	//
+	// Write our protocol version message
+	//
 
-  synchronized boolean closed() {
-    return closed;
-  }
-
-  //
-  // Read server's protocol version message
-  //
-
-  void readVersionMsg() throws Exception {
-
-    byte[] b = new byte[12];
+	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();
+	}
 
-    readFully(b);
+	//
+	// Negotiate the authentication scheme.
+	//
 
-    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");
-    }
+	int negotiateSecurity() throws Exception {
+		return (clientMinor >= 7) ? selectSecurityType() : readSecurityType();
+	}
 
-    serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
-    serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0');
+	//
+	// Read security type from the server (protocol version 3.3).
+	//
 
-    if (serverMajor < 3) {
-      throw new Exception("RFB server does not support protocol version 3");
-    }
-  }
-
+	int readSecurityType() throws Exception {
+		int secType = readU32();
 
-  //
-  // Write our protocol version message
-  //
+		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);
+		}
+	}
 
-  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();
-  }
+	//
+	// 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);
 
-  //
-  // Negotiate the authentication scheme.
-  //
+		// 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;
+			}
+		}
 
-  int negotiateSecurity() throws Exception {
-    return (clientMinor >= 7) ?
-      selectSecurityType() : readSecurityType();
-  }
+		// Find first supported security type.
+		for (int i = 0; i < nSecTypes; i++) {
+			if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) {
+				secType = secTypes[i];
+				break;
+			}
+		}
 
-  //
-  // Read security type from the server (protocol version 3.3).
-  //
-
-  int readSecurityType() throws Exception {
-    int secType = readU32();
+		if (secType == SecTypeInvalid) {
+			throw new Exception("Server did not offer supported security type");
+		} else {
+			os.write(secType);
+		}
 
-    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);
-    }
-  }
+		return secType;
+	}
+
+	//
+	// Perform "no authentication".
+	//
 
-  //
-  // Select security type from the server's list (protocol versions 3.7/3.8).
-  //
-
-  int selectSecurityType() throws Exception {
-    int secType = SecTypeInvalid;
+	void authenticateNone() throws Exception {
+		if (clientMinor >= 8)
+			readSecurityResult("No authentication");
+	}
 
-    // 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);
+	//
+	// 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
 
-    // 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;
-      }
-    }
+		// Truncate password on the first zero byte.
+		int firstZero = pw.indexOf(0);
+		if (firstZero != -1)
+			pw = pw.substring(0, firstZero);
 
-    // Find first supported security type.
-    for (int i = 0; i < nSecTypes; i++) {
-      if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) {
-	secType = secTypes[i];
-	break;
-      }
-    }
+		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);
 
-    if (secType == SecTypeInvalid) {
-      throw new Exception("Server did not offer supported security type");
-    } else {
-      os.write(secType);
-    }
+		os.write(challenge);
 
-    return secType;
-  }
+		readSecurityResult("VNC authentication");
+	}
 
-  //
-  // Perform "no authentication".
-  //
+	//
+	// Read security result.
+	// Throws an exception on authentication failure.
+	//
 
-  void authenticateNone() throws Exception {
-    if (clientMinor >= 8)
-      readSecurityResult("No authentication");
-  }
+	void readSecurityResult(String authType) throws Exception {
+		int securityResult = readU32();
 
-  //
-  // Perform standard VNC Authentication.
-  //
-
-  void authenticateVNC(String pw) throws Exception {
-    byte[] challenge = new byte[16];
-    readFully(challenge);
+		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);
+		}
+	}
 
-    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);
+	//
+	// Read the string describing the reason for a connection failure,
+	// and throw an exception.
+	//
 
-    byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
-    System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
-
-    DesCipher des = new DesCipher(key);
+	void readConnFailedReason() throws Exception {
+		int reasonLen = readU32();
+		byte[] reason = new byte[reasonLen];
+		readFully(reason);
+		throw new Exception(new String(reason));
+	}
 
-    des.encrypt(challenge, 0, challenge, 0);
-    des.encrypt(challenge, 8, challenge, 8);
-
-    os.write(challenge);
+	//
+	// Initialize capability lists (TightVNC protocol extensions).
+	//
 
-    readSecurityResult("VNC authentication");
-  }
+	void initCapabilities() {
+		tunnelCaps = new CapsContainer();
+		authCaps = new CapsContainer();
+		serverMsgCaps = new CapsContainer();
+		clientMsgCaps = new CapsContainer();
+		encodingCaps = new CapsContainer();
 
-  //
-  // Read security result.
-  // Throws an exception on authentication failure.
-  //
+		// Supported authentication methods
+		authCaps.add(AuthNone, StandardVendor, SigAuthNone, "No authentication");
+		authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
+				"Standard VNC password authentication");
 
-  void readSecurityResult(String authType) throws Exception {
-    int securityResult = readU32();
+		// Supported non-standard server-to-client messages
+		// [NONE]
+
+		// Supported non-standard client-to-server messages
+		// [NONE]
 
-    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.
-  //
+		// 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");
 
-  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).
-  //
+		// 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");
+	}
 
-  void initCapabilities() {
-    tunnelCaps    = new CapsContainer();
-    authCaps      = new CapsContainer();
-    serverMsgCaps = new CapsContainer();
-    clientMsgCaps = new CapsContainer();
-    encodingCaps  = new CapsContainer();
+	//
+	// Setup tunneling (TightVNC protocol extensions)
+	//
 
-    // 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]
+	void setupTunneling() throws IOException {
+		int nTunnelTypes = readU32();
+		if (nTunnelTypes != 0) {
+			readCapabilityList(tunnelCaps, nTunnelTypes);
 
-    // Supported non-standard client-to-server messages
-    // [NONE]
+			// We don't support tunneling yet.
+			writeInt(NoTunneling);
+		}
+	}
 
-    // 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");
+	//
+	// Negotiate authentication scheme (TightVNC protocol extensions)
+	//
+
+	int negotiateAuthenticationTight() throws Exception {
+		int nAuthTypes = readU32();
+		if (nAuthTypes == 0)
+			return AuthNone;
 
-    // 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");
-  }
+		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)
+	//
 
-  //
-  // Setup tunneling (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));
+		}
+	}
 
-  void setupTunneling() throws IOException {
-    int nTunnelTypes = readU32();
-    if (nTunnelTypes != 0) {
-      readCapabilityList(tunnelCaps, nTunnelTypes);
-
-      // We don't support tunneling yet.
-      writeInt(NoTunneling);
-    }
-  }
+	//
+	// Write a 32-bit integer into the output stream.
+	//
 
-  //
-  // Negotiate authentication scheme (TightVNC protocol extensions)
-  //
+	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);
+	}
 
-  int negotiateAuthenticationTight() throws Exception {
-    int nAuthTypes = readU32();
-    if (nAuthTypes == 0)
-      return AuthNone;
+	//
+	// Write the client initialisation message
+	//
 
-    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");
-  }
+	void writeClientInit() throws IOException {
+		if (viewer.options.shareDesktop) {
+			os.write(1);
+		} else {
+			os.write(0);
+		}
+		viewer.options.disableShareDesktop();
+	}
 
-  //
-  // Read a capability list (TightVNC protocol extensions)
-  //
+	//
+	// Read the server initialisation message
+	//
 
-  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));
-    }
-  }
+	String desktopName;
+	int framebufferWidth, framebufferHeight;
+	int bitsPerPixel, depth;
+	boolean bigEndian, trueColour;
+	int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
 
-  //
-  // 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 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);
 
-  void writeClientInit() throws IOException {
-    if (viewer.options.shareDesktop) {
-      os.write(1);
-    } else {
-      os.write(0);
-    }
-    viewer.options.disableShareDesktop();
-  }
+		// 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;
+	}
 
-  //
-  // Read the server initialisation message
-  //
+	//
+	// Create session file and write initial protocol messages into it.
+	//
 
-  String desktopName;
-  int framebufferWidth, framebufferHeight;
-  int bitsPerPixel, depth;
-  boolean bigEndian, trueColour;
-  int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
+	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;
 
-  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);
+		// 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.
+	//
 
-    // 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);
-    }
+	void closeSession() throws IOException {
+		if (rec != null) {
+			rec.close();
+			rec = null;
+		}
+	}
+
+	//
+	// Set new framebuffer size
+	//
 
-    inNormalProtocol = true;
-  }
-
+	void setFramebufferSize(int width, int height) {
+		framebufferWidth = width;
+		framebufferHeight = height;
+	}
 
-  //
-  // Create session file and write initial protocol messages into it.
-  //
+	//
+	// Read the server message type
+	//
+
+	int readServerMessageType() throws IOException {
+		int msgType = readU8();
 
-  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;
+		// 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();
+			}
+		}
 
-    // 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;
+		return msgType;
+	}
 
-    zlibWarningShown = false;
-    tightWarningShown = false;
-  }
+	//
+	// Read a FramebufferUpdate message
+	//
+
+	int updateNRects;
 
-  //
-  // Close session file.
-  //
+	void readFramebufferUpdate() throws IOException {
+		skipBytes(1);
+		updateNRects = readU16();
+		// System.out.println(updateNRects);
 
-  void closeSession() throws IOException {
-    if (rec != null) {
-      rec.close();
-      rec = null;
-    }
-  }
-
+		// If the session is being recorded:
+		if (rec != null) {
+			rec.writeByte(FramebufferUpdate);
+			rec.writeByte(0);
+			rec.writeShortBE(updateNRects);
+		}
 
-  //
-  // Set new framebuffer size
-  //
+		numUpdatesInSession++;
+	}
+
+	// Read a FramebufferUpdate rectangle header
+
+	int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
 
-  void setFramebufferSize(int width, int height) {
-    framebufferWidth = width;
-    framebufferHeight = height;
-  }
-
-
-  //
-  // Read the server message type
-  //
-
-  int readServerMessageType() throws IOException {
-    int msgType = readU8();
+	void readFramebufferUpdateRectHdr() throws Exception {
+		updateRectX = readU16();
+		updateRectY = readU16();
+		updateRectW = readU16();
+		updateRectH = readU16();
+		updateRectEncoding = readU32();
+		// System.out.println("readU16&32");
 
-    // 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;
+		if (updateRectEncoding == EncodingZlib
+				|| updateRectEncoding == EncodingZRLE
+				|| updateRectEncoding == EncodingTight)
+			wereZlibUpdates = true;
 
-  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);
-    }
+		// 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;
+				}
+			}
+		}
 
-    numUpdatesInSession++;
-  }
-
-  // Read a FramebufferUpdate rectangle header
-
-  int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
+		if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding)
+			return;
 
-  void readFramebufferUpdateRectHdr() throws Exception {
-    updateRectX = readU16();
-    updateRectY = readU16();
-    updateRectW = readU16();
-    updateRectH = readU16();
-    updateRectEncoding = readU32();
-//    System.out.println("readU16&32");
+		if (updateRectX + updateRectW > framebufferWidth
+				|| updateRectY + updateRectH > framebufferHeight) {
+			throw new Exception("Framebuffer update rectangle too large: "
+					+ updateRectW + "x" + updateRectH + " at (" + updateRectX
+					+ "," + updateRectY + ")");
+		}
+	}
 
-    if (updateRectEncoding == EncodingZlib ||
-        updateRectEncoding == EncodingZRLE ||
-	updateRectEncoding == EncodingTight)
-      wereZlibUpdates = true;
+	// 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) {
-      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;
+		// If the session is being recorded:
+		if (rec != null) {
+			rec.writeShortBE(copyRectSrcX);
+			rec.writeShortBE(copyRectSrcY);
+		}
 	}
-	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;
+
+	//
+	// 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.
+	//
 
-    if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding)
-      return;
+	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 (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 (rec != null && recordFromBeginning)
+			for (int i = 0; i < byteCount; i++)
+				rec.writeByte(portion[i]);
 
-    // If the session is being recorded:
-    if (rec != null) {
-      rec.writeShortBE(copyRectSrcX);
-      rec.writeShortBE(copyRectSrcY);
-    }
-  }
+		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];
 
-  //
-  // Read a ServerCutText message
-  //
-
-  String readServerCutText() throws IOException {
-    skipBytes(3);
-    int len = readU32();
-    byte[] text = new byte[len];
-    readFully(text);
-    return new String(text);
-  }
-
+		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);
 
-  //
-  // 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.
-  //
+		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];
 
-  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;
-      }
-    }
+		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;
 
-    if (rec != null && recordFromBeginning)
-      for (int i = 0; i < byteCount; i++)
-	rec.writeByte(portion[i]);
+		os.write(b);
+	}
 
-    return len;
-  }
-
+	//
+	// Write a FixColourMapEntries message. The values in the red, green and
+	// blue arrays are from 0 to 65535.
+	//
 
-  //
-  // Write a FramebufferUpdateRequest message
-  //
+	void writeFixColourMapEntries(int firstColour, int nColours, int[] red,
+			int[] green, int[] blue) throws IOException {
+		byte[] b = new byte[6 + nColours * 6];
 
-  void writeFramebufferUpdateRequest(int x, int y, int w, int h,
-				     boolean incremental)
-       throws IOException
-  {
-    byte[] b = new byte[10];
+		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);
 
-    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);
+		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);
-  }
-
+		os.write(b);
+	}
 
-  //
-  // Write a SetPixelFormat message
-  //
+	//
+	// Write a SetEncodings 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];
+	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);
 
-    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;
+		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
+	//
 
-    os.write(b);
-  }
-
-
-  //
-  // Write a FixColourMapEntries message.  The values in the red, green and
-  // blue arrays are from 0 to 65535.
-  //
+	void writeClientCutText(String text) throws IOException {
+		byte[] b = new byte[8 + text.length()];
 
-  void writeFixColourMapEntries(int firstColour, int nColours,
-				int[] red, int[] green, int[] blue)
-       throws IOException
-  {
-    byte[] b = new byte[6 + nColours * 6];
+		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);
 
-    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);
+		System.arraycopy(text.getBytes(), 0, b, 8, text.length());
+
+		os.write(b);
+	}
 
-    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);
-  }
+	//
+	// 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.
 
-
-  //
-  // Write a SetEncodings message
-  //
+	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;
 
-  void writeSetEncodings(int[] encs, int len) throws IOException {
-    byte[] b = new byte[4 + 4 * len];
+	//
+	// Write a pointer event message. We may need to send modifier key events
+	// around it to set the correct modifier state.
+	//
 
-    b[0] = (byte) SetEncodings;
-    b[2] = (byte) ((len >> 8) & 0xff);
-    b[3] = (byte) (len & 0xff);
+	int pointerMask = 0;
+
+	void writePointerEvent(MouseEvent evt) throws IOException {
+		int modifiers = evt.getModifiers();
 
-    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);
-    }
+		int mask2 = 2;
+		int mask3 = 4;
+		if (viewer.options.reverseMouseButtons2And3) {
+			mask2 = 4;
+			mask3 = 2;
+		}
 
-    os.write(b);
-  }
-
-
-  //
-  // Write a ClientCutText message
-  //
-
-  void writeClientCutText(String text) throws IOException {
-    byte[] b = new byte[8 + text.length()];
+		// 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.
 
-    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);
-  }
-
+		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;
+			}
+		}
 
-  //
-  // 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.
-  //
+		eventBufLen = 0;
+		writeModifierKeyEvents(modifiers);
 
-  byte[] eventBuf = new byte[72];
-  int eventBufLen;
+		int x = evt.getX();
+		int y = evt.getY();
 
-
-  // Useful shortcuts for modifier masks.
+		if (x < 0)
+			x = 0;
+		if (y < 0)
+			y = 0;
 
-  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.
-  //
+		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);
 
-  int pointerMask = 0;
-
-  void writePointerEvent(MouseEvent evt) throws IOException {
-    int modifiers = evt.getModifiers();
+		//
+		// Always release all modifiers after an "up" event
+		//
 
-    int mask2 = 2;
-    int mask3 = 4;
-    if (viewer.options.reverseMouseButtons2And3) {
-      mask2 = 4;
-      mask3 = 2;
-    }
+		if (pointerMask == 0) {
+			writeModifierKeyEvents(0);
+		}
+
+		os.write(eventBuf, 0, eventBufLen);
+	}
 
-    // 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.
+	//
+	// 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.
+		//
 
-    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;
-      }
-    }
+		// 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?
+		//
 
-    eventBufLen = 0;
-    writeModifierKeyEvents(modifiers);
+		boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
 
-    int x = evt.getX();
-    int y = evt.getY();
-
-    if (x < 0) x = 0;
-    if (y < 0) y = 0;
+		int key;
+		if (evt.isActionKey()) {
 
-    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);
-  }
-
+			//
+			// An action key should be one of the following.
+			// If not then just ignore the event.
+			//
 
-  //
-  // 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;
+			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;
+			}
 
-    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.
-      //
+		} else {
 
-      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;
-      }
+			//
+			// 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.
+			//
 
-    } 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;
+			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;
-      }
-    }
+			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;
+		// 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) {
+				// 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;
+		}
 
-      if (!down)
-	brokenKeyPressed = false;  
-    }
-
-    eventBufLen = 0;
-    writeModifierKeyEvents(evt.getModifiers());
-    writeKeyEvent(key, down);
+		eventBufLen = 0;
+		writeModifierKeyEvents(evt.getModifiers());
+		writeKeyEvent(key, down);
 
-    // Always release all modifiers after an "up" event
-    if (!down)
-      writeModifierKeyEvents(0);
+		// Always release all modifiers after an "up" event
+		if (!down)
+			writeModifierKeyEvents(0);
 
-    os.write(eventBuf, 0, eventBufLen);
-  }
+		os.write(eventBuf, 0, eventBufLen);
+	}
 
+	//
+	// Add a raw key event with the given X keysym to eventBuf.
+	//
 
-  //
-  // 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);
+	}
 
-  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;
 
-  //
-  // Write key events to set the correct modifier state.
-  //
+	void writeModifierKeyEvents(int newModifiers) {
+		if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
+			writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
 
-  int oldModifiers = 0;
+		if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK))
+			writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0);
 
-  void writeModifierKeyEvents(int newModifiers) {
-    if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
-      writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
+		if ((newModifiers & META_MASK) != (oldModifiers & META_MASK))
+			writeKeyEvent(0xffe7, (newModifiers & META_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);
 
-    if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK))
-      writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0);
+		oldModifiers = newModifiers;
+	}
 
-    oldModifiers = newModifiers;
-  }
+	//
+	// Compress and write the data into the recorded session file. This
+	// method assumes the recording is on (rec != null).
+	//
 
-  //
-  // 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, 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);
-  }
+	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).
-  //
+	//
+	// 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);
-  }
+	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;
+	public void startTiming() {
+		timing = true;
 
-    // Carry over up to 1s worth of previous rate for smoothing.
+		// Carry over up to 1s worth of previous rate for smoothing.
 
-    if (timeWaitedIn100us > 10000) {
-      timedKbits = timedKbits * 10000 / timeWaitedIn100us;
-      timeWaitedIn100us = 10000;
-    }
-  }
+		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 void stopTiming() {
+		timing = false;
+		if (timeWaitedIn100us < timedKbits / 2)
+			timeWaitedIn100us = timedKbits / 2; // upper limit 20Mbit/s
+	}
 
-  public long kbitsPerSecond() {
-    return timedKbits * 10000 / timeWaitedIn100us;
-  }
+	public long kbitsPerSecond() {
+		return timedKbits * 10000 / timeWaitedIn100us;
+	}
 
-  public long timeWaited() {
-    return 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.
-  //
+	//
+	// 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[]) 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();
+	public void readFully(byte b[], int off, int len) throws IOException {
+		long before = 0;
+		if (timing)
+			before = System.currentTimeMillis();
 
-    is.readFully(b, off, len);
+		is.readFully(b, off, len);
 
-    if (timing) {
-      long after = System.currentTimeMillis();
-      long newTimeWaited = (after - before) * 10;
-      int newKbits = len * 8 / 1000;
+		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
+			// limit rate to between 10kbit/s and 40Mbit/s
 
-      if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
-      if (newTimeWaited < newKbits/4)    newTimeWaited = newKbits/4;
+			if (newTimeWaited > newKbits * 1000)
+				newTimeWaited = newKbits * 1000;
+			if (newTimeWaited < newKbits / 4)
+				newTimeWaited = newKbits / 4;
 
-      timeWaitedIn100us += newTimeWaited;
-      timedKbits += newKbits;
-    }
+			timeWaitedIn100us += newTimeWaited;
+			timedKbits += newKbits;
+		}
 
-    numBytesRead += len;
-  }
+		numBytesRead += len;
+	}
 
-  final int available() throws IOException {
-    return is.available();
-  }
+	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;
-  }
+	// 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 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 readU16() throws IOException {
+		int r = is.readUnsignedShort();
+		numBytesRead += 2;
+		return r;
+	}
 
-  final int readU32() throws IOException {
-    int r = is.readInt();
-    numBytesRead += 4;
-    return r;
-  }
-  
+	final int readU32() throws IOException {
+		int r = is.readInt();
+		numBytesRead += 4;
+		return r;
+	}
+
 }
-
--- a/src/VncCanvas.java	Wed Apr 13 07:48:49 2011 +0900
+++ b/src/VncCanvas.java	Wed Apr 13 08:00:53 2011 +0900
@@ -33,1854 +33,1858 @@
 // 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;
+class VncCanvas extends Canvas implements KeyListener, MouseListener,
+		MouseMotionListener {
 
-  int maxWidth = 0, maxHeight = 0;
-  int scalingFactor;
-  int scaledWidth, scaledHeight;
+	VncViewer viewer;
+	RfbProto rfb;
+	ColorModel cm8, cm24;
+	Color[] colors;
+	int bytesPixel;
 
-  Image memImage;
-  Graphics memGraphics;
+	int maxWidth = 0, maxHeight = 0;
+	int scalingFactor;
+	int scaledWidth, scaledHeight;
 
-  Image rawPixelsImage;
-  MemoryImageSource pixelsSource;
-  byte[] pixels8;
-  int[] pixels24;
+	Image memImage;
+	Graphics memGraphics;
 
-  // 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
+	Image rawPixelsImage;
+	MemoryImageSource pixelsSource;
+	byte[] pixels8;
+	int[] pixels24;
 
-  // 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;
+	// 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
 
-  // 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.
-  //
+	// ZRLE encoder's data.
+	byte[] zrleBuf;
+	int zrleBufLen = 0;
+	byte[] zrleTilePixels8;
+	int[] zrleTilePixels24;
+	ZlibInStream zrleInStream;
+	boolean zrleRecWarningShown = false;
 
-  public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
-    throws IOException {
-
-    viewer = v;
-    maxWidth = maxWidth_;
-    maxHeight = maxHeight_;
+	// Zlib encoder's data.
+	byte[] zlibBuf;
+	int zlibBufLen = 0;
+	Inflater zlibInflater;
 
-    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);
+	// Tight encoder's data.
+	final static int tightZlibBufferSize = 512;
+	Inflater[] tightInflaters;
 
-    colors = new Color[256];
-    for (int i = 0; i < 256; i++)
-      colors[i] = new Color(cm8.getRGB(i));
-
-    setPixelFormat();
+	// 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;
 
-    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);
-  }
+	// True if we process keyboard and mouse events.
+	boolean inputEnabled;
 
-  public VncCanvas(VncViewer v) throws IOException {
-    this(v, 0, 0);
-  }
-
-  //
-  // Callback methods to determine geometry of our Component.
-  //
+	//
+	// The constructors.
+	//
 
-  public Dimension getPreferredSize() {
-    return new Dimension(scaledWidth, scaledHeight);
-  }
+	public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
+			throws IOException {
 
-  public Dimension getMinimumSize() {
-    return new Dimension(scaledWidth, scaledHeight);
-  }
+		viewer = v;
+		maxWidth = maxWidth_;
+		maxHeight = maxHeight_;
 
-  public Dimension getMaximumSize() {
-    return new Dimension(scaledWidth, scaledHeight);
-  }
+		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);
 
-  //
-  // All painting is performed here.
-  //
+		colors = new Color[256];
+		for (int i = 0; i < 256; i++)
+			colors[i] = new Color(cm8.getRGB(i));
 
-  public void update(Graphics g) {
-    paint(g);
-  }
+		setPixelFormat();
+
+		inputEnabled = false;
+		if (!viewer.options.viewOnly)
+			enableInput(true);
 
-  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);
-      }
-    }
-  }
+		// 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);
+	}
 
-  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.
-  //
+	//
+	// Callback methods to determine geometry of our Component.
+	//
 
-  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();
-	  }
+	public Dimension getPreferredSize() {
+		return new Dimension(scaledWidth, scaledHeight);
+	}
+
+	public Dimension getMinimumSize() {
+		return new Dimension(scaledWidth, scaledHeight);
 	}
-      }
-      return false;		// All image data was processed.
-    }
-  }
+
+	public Dimension getMaximumSize() {
+		return new Dimension(scaledWidth, scaledHeight);
+	}
 
-  //
-  // 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.
-  //
+	//
+	// All painting is performed here.
+	//
+
+	public void update(Graphics g) {
+		paint(g);
+	}
 
-  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 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 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() {
+	public void paintScaledFrameBuffer(Graphics g) {
+		g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
+	}
 
-    // 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 + "%");
-    }
+	//
+	// Override the ImageObserver interface method to handle drawing of
+	// JPEG-encoded data.
+	//
 
-    // 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) {
+	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.
+		}
+	}
 
-      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);
+	//
+	// 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.
+	//
 
-    // 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;
+	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
+		}
+	}
 
-    // 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.
+	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();
+	}
 
-    screenSize.height -= 30;
-    screenSize.width  -= 30;
+	void updateFramebufferSize() {
 
-    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);
-    }
+		// Useful shortcuts.
+		int fbWidth = rfb.framebufferWidth;
+		int fbHeight = rfb.framebufferHeight;
 
-    viewer.desktopScrollPane.doLayout();
-  }
+		// 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 + "%");
+		}
 
-  //
-  // processNormalProtocol() - executed by the rfbThread to deal with the
-  // RFB socket.
-  //
-
-  public void processNormalProtocol() throws Exception {
-
-    // Start/stop session recording if necessary.
-    viewer.checkRecordingStatus();
+		// Update scaled framebuffer geometry.
+		scaledWidth = (fbWidth * scalingFactor + 50) / 100;
+		scaledHeight = (fbHeight * scalingFactor + 50) / 100;
 
-    rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
-				      rfb.framebufferHeight, false);
-
-    resetStats();
-    boolean statsRestarted = false;
-
-    //
-    // main dispatch loop
-    //
-    
-    long count = 0;
+		// 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();
+			}
+		}
 
-    while (true) {
+		// Images with raw pixels should be re-allocated on every change
+		// of geometry or pixel format.
+		if (bytesPixel == 1) {
 
-      System.out.println("\ncount="+count);
-      count++;
-      System.out.println("rfb.available()="+rfb.available());
-      while(rfb.available() == 0)continue;
-      
-      // Read message type from the server.
-      int msgType = rfb.readServerMessageType();
-      
-      
-      // Process the message depending on its type.
-      switch (msgType) {
-      case RfbProto.FramebufferUpdate:
+			pixels24 = null;
+			pixels8 = new byte[fbWidth * fbHeight];
 
-        if (statNumUpdates == viewer.debugStatsExcludeUpdates &&
-            !statsRestarted) {
-          resetStats();
-          statsRestarted = true;
-        } else if (statNumUpdates == viewer.debugStatsMeasureUpdates &&
-                   statsRestarted) {
-          viewer.disconnect();
-        }
+			pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm8,
+					pixels8, 0, fbWidth);
 
-	rfb.readFramebufferUpdate();
-	statNumUpdates++;
+			zrleTilePixels24 = null;
+			zrleTilePixels8 = new byte[64 * 64];
 
-	boolean cursorPosReceived = false;
+		} else {
 
-	for (int i = 0; i < rfb.updateNRects; i++) {
+			pixels8 = null;
+			pixels24 = new int[fbWidth * fbHeight];
 
-	  rfb.readFramebufferUpdateRectHdr();
-	  statNumTotalRects++;
-	  int rx = rfb.updateRectX, ry = rfb.updateRectY;
-	  int rw = rfb.updateRectW, rh = rfb.updateRectH;
+			pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24,
+					pixels24, 0, fbWidth);
 
-	  if (rfb.updateRectEncoding == rfb.EncodingLastRect)
-	    break;
+			zrleTilePixels8 = null;
+			zrleTilePixels24 = new int[64 * 64];
 
-	  if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
-	    rfb.setFramebufferSize(rw, rh);
-	    updateFramebufferSize();
-	    break;
-	  }
+		}
+		pixelsSource.setAnimated(true);
+		rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
 
-	  if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
-	      rfb.updateRectEncoding == rfb.EncodingRichCursor) {
-	    handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
-	    continue;
-	  }
+		// Update the size of desktop containers.
+		if (viewer.inSeparateFrame) {
+			if (viewer.desktopScrollPane != null)
+				resizeDesktopFrame();
+		} else {
+			setSize(scaledWidth, scaledHeight);
+		}
+		viewer.moveFocusToDesktop();
+	}
 
-	  if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
-	    softCursorMove(rx, ry);
-	    cursorPosReceived = true;
-	    continue;
-	  }
+	void resizeDesktopFrame() {
+		setSize(scaledWidth, scaledHeight);
 
-          long numBytesReadBefore = rfb.getNumBytesRead();
+		// 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));
 
-          rfb.startTiming();
+		viewer.vncFrame.pack();
+
+		// Try to limit the frame size to the screen size.
 
-	  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);
-	  }
+		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;
 
-          rfb.stopTiming();
+		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);
+		}
 
-          statNumPixelRects++;
-          statNumBytesDecoded += rw * rh * bytesPixel;
-          statNumBytesEncoded +=
-            (int)(rfb.getNumBytesRead() - numBytesReadBefore);
+		viewer.desktopScrollPane.doLayout();
 	}
 
-	boolean fullUpdateNeeded = false;
+	//
+	// 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) {
+
+			System.out.println("\ncount=" + count);
+			count++;
+			System.out.println("rfb.available()=" + rfb.available());
+			while (rfb.available() == 0)
+				continue;
+
+			// 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.updateRectEncoding == rfb.EncodingLastRect)
+						break;
 
-	// Start/stop session recording if necessary. Request full
-	// update if a new session file was opened.
-	if (viewer.checkRecordingStatus())
-	  fullUpdateNeeded = true;
+					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) {
-	    }
-	  }
+				// 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);
 	}
 
-        viewer.autoSelectEncodings();
+	//
+	// 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);
+		}
 
-	// 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;
+		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);
+
+		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;
 	}
 
-        // 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;
+	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));
+				/*
+				 * dst[i] = (0x00 << 16 | 0x00 << 8 | 0xFF);
+				 */
 
-      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);
+	void readZrlePalette(int[] palette, int palSize) throws Exception {
+		readPixels(zrleInStream, palette, palSize);
 	}
-      }
-    } 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);
+
+	void readZrleRawPixels(int tw, int th) throws Exception {
+		if (bytesPixel == 1) {
+			zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
+		} else {
+			readPixels(zrleInStream, zrleTilePixels24, tw * th); // /
+		}
 	}
-	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);
-	}
-      }
-    }
+
+	void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
+			throws Exception {
 
-    handleUpdatedPixels(x, y, w, h);
-    if (paint)
-      scheduleRepaint(x, y, w, h);
-  }
-
-  //
-  // Handle a CopyRect rectangle.
-  //
+		int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4
+				: ((palSize > 2) ? 2 : 1)));
+		int ptr = 0;
 
-  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);
-  }
+		for (int i = 0; i < th; i++) {
+			int eol = ptr + tw;
+			int b = 0;
+			int nbits = 0;
 
-  //
-  // Handle an RRE-encoded rectangle.
-  //
-
-  void handleRRERect(int x, int y, int w, int h) throws IOException {
-
-    int nSubrects = rfb.readU32();
+			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];
+				}
+			}
+		}
+	}
 
-    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);
+	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);
 
-    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;
+			if (!(len <= end - ptr))
+				throw new Exception("ZRLE decoder: assertion failed"
+						+ " (len <= end-ptr)");
 
-    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();
+			if (bytesPixel == 1) {
+				while (len-- > 0)
+					zrleTilePixels8[ptr++] = (byte) pix;
+			} else {
+				while (len-- > 0)
+					zrleTilePixels24[ptr++] = pix;
+			}
+		}
+	}
 
-      memGraphics.setColor(pixel);
-      memGraphics.fillRect(sx, sy, sw, sh);
-    }
-
-    scheduleRepaint(x, y, w, h);
-  }
-
-  //
-  // Handle a CoRRE-encoded rectangle.
-  //
+	void readZrlePackedRLEPixels(int tw, int th, int[] palette)
+			throws Exception {
 
-  void handleCoRRERect(int x, int y, int w, int h) throws IOException {
-    int nSubrects = rfb.readU32();
+		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);
 
-    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);
+				if (!(len <= end - ptr))
+					throw new Exception("ZRLE decoder: assertion failed"
+							+ " (len <= end - ptr)");
+			}
 
-    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;
+			index &= 127;
+			int pix = palette[index];
 
-    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);
-  }
+			if (bytesPixel == 1) {
+				while (len-- > 0)
+					zrleTilePixels8[ptr++] = (byte) pix;
+			} else {
+				while (len-- > 0)
+					zrleTilePixels24[ptr++] = pix;
+			}
+		}
+	}
 
-  //
-  // 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;
+	//
+	// Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
+	//
 
-	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);
-    }
+	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);
+	}
 
-    // Is it a raw-encoded sub-rectangle?
-    if ((subencoding & rfb.HextileRaw) != 0) {
-      handleRawRect(tx, ty, tw, th, false);
-      return;
-    }
+	//
+	// Handle a Zlib-encoded rectangle.
+	//
+
+	void handleZlibRect(int x, int y, int w, int h) throws Exception {
+
+		int nBytes = rfb.readU32();
 
-    // 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);
+		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);
 
-    // 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;
+		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);
+			}
+		}
 
-    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);
-    }
+		handleUpdatedPixels(x, y, w, h);
+		scheduleRepaint(x, y, w, h);
+	}
 
-    int b1, b2, sx, sy, sw, sh;
-    int i = 0;
-
-    if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
+	//
+	// Handle a Tight-encoded rectangle.
+	//
 
-      // 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) {
+	void handleTightRect(int x, int y, int w, int h) throws Exception {
 
-      // 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 {
+		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);
+			}
+		}
 
-      // 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);
-      }
+		// 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 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();
+		// Handle solid-color rectangles.
+		if (comp_ctl == rfb.TightFill) {
 
-    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);
-    
-
-    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);
+			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;
 
-        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 (comp_ctl == rfb.TightJpeg) {
+
+			statNumRectsTightJPEG++;
 
-        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);
-      }
-    }
+			// 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);
+			}
 
-    zrleInStream.reset();
-
-    scheduleRepaint(x, y, w, h);
-  }
+			// Create an Image object from the JPEG data.
+			Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
 
-  int readPixel(InStream is) throws Exception {
-    int pix;
-    
-    if (bytesPixel == 1) {
+			// Remember the rectangle where the image should be drawn.
+			jpegRect = new Rectangle(x, y, w, h);
 
-    	
-    	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;
-  }
+			// 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");
+				}
+			}
 
-  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));
-    	  /*
-  	      dst[i] = (0x00 << 16 |
-                     0x00 << 8 |
-                     0xFF);
-    	   */
+			// Done, jpegRect is not needed any more.
+			jpegRect = null;
+			return;
 
-
-      
-      }
-    }
-  }
-
-  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)");
+		// 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;
 
-      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);
+		// 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 (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 {
+			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;
+						}
+					}
+				}
+			}
+		}
 
-    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);
-    }
+		handleUpdatedPixels(x, y, w, h);
+		scheduleRepaint(x, y, w, h);
+	}
 
-    // 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++;
+	//
+	// Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
+	//
 
-      // 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);
-      }
+	void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
 
-      // 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);
+		int dx, dy, n;
+		int i = y * rfb.framebufferWidth + x;
+		int rowBytes = (w + 7) / 8;
+		byte b;
 
-      // 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");
+		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) {
 
-      // Done, jpegRect is not needed any more.
-      jpegRect = null;
-      return;
-
-    }
+		int dx, dy, n;
+		int i = y * rfb.framebufferWidth + x;
+		int rowBytes = (w + 7) / 8;
+		byte b;
 
-    // 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);
+		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);
+		}
 	}
-        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;
+
+	//
+	// 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++) {
 
-    // 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);
+			/* 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);
+		}
 	}
-	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);
-	    }
-	  }
+
+	//
+	// 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);
 	}
-      }
-    } 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);
-      }
+
+	//
+	// 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);
+		}
+	}
 
-      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];
-	    }
-	  }
+	//
+	// Handle events.
+	//
+
+	public void keyPressed(KeyEvent evt) {
+		processLocalKeyEvent(evt);
+	}
+
+	public void keyReleased(KeyEvent evt) {
+		processLocalKeyEvent(evt);
+	}
+
+	public void keyTyped(KeyEvent evt) {
+		evt.consume();
 	}
-      } 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;
-	    }
-	  }
+
+	public void mousePressed(MouseEvent evt) {
+		processLocalMouseEvent(evt, false);
 	}
-      }
-    }
 
-    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;
+	public void mouseReleased(MouseEvent evt) {
+		processLocalMouseEvent(evt, false);
+	}
 
-    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);
-    }
-  }
+	public void mouseMoved(MouseEvent evt) {
+		processLocalMouseEvent(evt, true);
+	}
 
-  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;
+	public void mouseDragged(MouseEvent evt) {
+		processLocalMouseEvent(evt, true);
+	}
 
-    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);
+	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();
+	}
 
-      /* 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];
+	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();
+			}
+		}
 	}
-	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);
-    }
-  }
+	//
+	// Ignored events.
+	//
 
-  //
-  // Display newly updated area of pixels.
-  //
-
-  void handleUpdatedPixels(int x, int y, int w, int h) {
+	public void mouseClicked(MouseEvent evt) {
+	}
 
-    // 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);
-  }
+	public void mouseEntered(MouseEvent evt) {
+	}
 
-  //
-  // Tell JVM to repaint specified desktop area.
-  //
+	public void mouseExited(MouseEvent evt) {
+	}
+
+	//
+	// Reset update statistics.
+	//
 
-  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);
-    }
-  }
+	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 events.
-  //
+	// ////////////////////////////////////////////////////////////////
+	//
+	// Handle cursor shape updates (XCursor and RichCursor encodings).
+	//
 
-  public void keyPressed(KeyEvent evt) {
-    processLocalKeyEvent(evt);
-  }
-  public void keyReleased(KeyEvent evt) {
-    processLocalKeyEvent(evt);
-  }
-  public void keyTyped(KeyEvent evt) {
-    evt.consume();
-  }
+	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;
 
-  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);
-  }
+		// 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;
+		}
 
-  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();
-	  }
+		// 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);
 	}
-      } 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;
-  }
+	//
+	// decodeCursorShape(). Decode cursor pixel data and return
+	// corresponding MemoryImageSource instance.
+	//
 
-  //////////////////////////////////////////////////////////////////
-  //
-  // Handle cursor shape updates (XCursor and RichCursor encodings).
-  //
+	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];
 
-  boolean showSoftCursor = false;
-
-  MemoryImageSource softCursorSource;
-  Image softCursor;
-
-  int cursorX = 0, cursorY = 0;
-  int cursorWidth, cursorHeight;
-  int origCursorWidth, origCursorHeight;
-  int hotX, hotY;
-  int origHotX, origHotY;
+		if (encodingType == rfb.EncodingXCursor) {
 
-  //
-  // Handle cursor shape update (XCursor and RichCursor encodings).
-  //
+			// 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)) };
 
-  synchronized void
-    handleCursorShapeUpdate(int encodingType,
-			    int xhot, int yhot, int width, int height)
-    throws IOException {
-
-    softCursorFree();
+			// Read pixel and mask data.
+			byte[] pixBuf = new byte[bytesMaskData];
+			rfb.readFully(pixBuf);
+			byte[] maskBuf = new byte[bytesMaskData];
+			rfb.readFully(maskBuf);
 
-    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);
+			// 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;
+				}
+			}
 
-    // Set original (non-scaled) cursor dimensions.
-    origCursorWidth = width;
-    origCursorHeight = height;
-    origHotX = xhot;
-    origHotY = yhot;
-
-    // Create off-screen cursor image.
-    createSoftCursor();
+		} else {
+			// encodingType == rfb.EncodingRichCursor
 
-    // 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;
+			// Read pixel and mask data.
+			byte[] pixBuf = new byte[width * height * bytesPixel];
+			rfb.readFully(pixBuf);
+			byte[] maskBuf = new byte[bytesMaskData];
+			rfb.readFully(maskBuf);
 
-    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++) {
+					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;
+				}
+			}
 
-      // 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;
-	  }
+		return new MemoryImageSource(width, height, softCursorPixels, 0, width);
 	}
-	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;
-	}
-      }
+
+	//
+	// createSoftCursor(). Assign softCursor new Image (scaled if necessary).
+	// Uses softCursorSource as a source for new cursor image.
+	//
 
-    }
+	synchronized void createSoftCursor() {
 
-    return new MemoryImageSource(width, height, softCursorPixels, 0, width);
-  }
+		if (softCursorSource == null)
+			return;
+
+		int scaleCursor = viewer.options.scaleCursor;
+		if (scaleCursor == 0 || !inputEnabled)
+			scaleCursor = 100;
 
-  //
-  // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
-  // Uses softCursorSource as a source for new cursor image.
-  //
-
-  synchronized void
-    createSoftCursor() {
-
-    if (softCursorSource == null)
-      return;
+		// Save original cursor coordinates.
+		int x = cursorX - hotX;
+		int y = cursorY - hotY;
+		int w = cursorWidth;
+		int h = cursorHeight;
 
-    int scaleCursor = viewer.options.scaleCursor;
-    if (scaleCursor == 0 || !inputEnabled)
-      scaleCursor = 100;
+		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);
 
-    // 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 (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);
 
-    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);
+		}
+	}
 
-      repaint(viewer.deferCursorUpdates, x, y, w, h);
-    }
-  }
-
-  //
-  // softCursorMove(). Moves soft cursor into a particular location.
-  //
+	//
+	// 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);
-    }
-  }
+	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.
-  //
+	//
+	// softCursorFree(). Remove soft cursor, dispose resources.
+	//
 
-  synchronized void softCursorFree() {
-    if (showSoftCursor) {
-      showSoftCursor = false;
-      softCursor = null;
-      softCursorSource = null;
+	synchronized void softCursorFree() {
+		if (showSoftCursor) {
+			showSoftCursor = false;
+			softCursor = null;
+			softCursorSource = null;
 
-      repaint(viewer.deferCursorUpdates,
-	      cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
-    }
-  }
+			repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
+					cursorWidth, cursorHeight);
+		}
+	}
 }
--- a/src/VncViewer.java	Wed Apr 13 07:48:49 2011 +0900
+++ b/src/VncViewer.java	Wed Apr 13 08:00:53 2011 +0900
@@ -25,1064 +25,1049 @@
 // a VNC desktop.
 //
 
-	import java.awt.*;
-	import java.awt.event.*;
-	import java.io.*;
-	import java.net.*;
+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 {
+public class VncViewer extends java.applet.Applet implements
+		java.lang.Runnable, WindowListener {
 
-  boolean inAnApplet = true;
-  boolean inSeparateFrame = false;
+	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.
-  //
+	//
+	// 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();
-  }
+	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;
+	String[] mainArgs;
+
+	RfbProto rfb;
+	Thread rfbThread;
 
-  RfbProto rfb;
-  Thread rfbThread;
+	Frame vncFrame;
+	Container vncContainer;
+	ScrollPane desktopScrollPane;
+	GridBagLayout gridbag;
+	ButtonPanel buttonPanel;
+	Label connStatusLabel;
+	VncCanvas vc;
+	OptionsFrame options;
+	ClipboardFrame clipboard;
+	RecordingFrame rec;
 
-  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;
+	// 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;
+	// 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;
+	// Reference to this applet for inter-applet communication.
+	public static java.applet.Applet refApplet;
 
-  //
-  // init()
-  //
+	//
+	// init()
+	//
 
-  public void init() {
+	public void init() {
 
-    readParameters();
+		readParameters();
 
-    refApplet = this;
+		refApplet = this;
 
-    if (inSeparateFrame) {
-      vncFrame = new Frame("TightVNC");
-      if (!inAnApplet) {
-	vncFrame.add("Center", this);
-      }
-      vncContainer = vncFrame;
-    } else {
-      vncContainer = this;
-    }
+		if (inSeparateFrame) {
+			vncFrame = new Frame("TightVNC");
+			if (!inAnApplet) {
+				vncFrame.add("Center", this);
+			}
+			vncContainer = vncFrame;
+		} else {
+			vncContainer = this;
+		}
 
-    recordingSync = new Object();
+		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;
+		options = new OptionsFrame(this);
+		clipboard = new ClipboardFrame(this);
+		if (RecordingFrame.checkSecurity())
+			rec = new RecordingFrame(this);
 
-    if (inSeparateFrame)
-      vncFrame.addWindowListener(this);
+		sessionFileName = null;
+		recordingActive = false;
+		recordingStatusChanged = false;
+		cursorUpdatesDef = null;
+		eightBitColorsDef = null;
 
-    rfbThread = new Thread(this);
-    rfbThread.start();
-  }
-
-  public void update(Graphics g) {
-  }
+		if (inSeparateFrame)
+			vncFrame.addWindowListener(this);
 
-  //
-  // run() - executed by the rfbThread to deal with the RFB socket.
-  //
-
-  public void run() {
+		rfbThread = new Thread(this);
+		rfbThread.start();
+	}
 
-    gridbag = new GridBagLayout();
-    vncContainer.setLayout(gridbag);
+	public void update(Graphics g) {
+	}
 
-    GridBagConstraints gbc = new GridBagConstraints();
-    gbc.gridwidth = GridBagConstraints.REMAINDER;
-    gbc.anchor = GridBagConstraints.NORTHWEST;
+	//
+	// run() - executed by the rfbThread to deal with the RFB socket.
+	//
+
+	public void run() {
 
-    if (showControls) {
-      buttonPanel = new ButtonPanel(this);
-      gridbag.setConstraints(buttonPanel, gbc);
-      vncContainer.add(buttonPanel);
-    }
+		gridbag = new GridBagLayout();
+		vncContainer.setLayout(gridbag);
 
-    
-    
-    /*****************************************************************************/
-    vncFrame.pack();
-    vncFrame.setVisible(true);
-    try{
-    	rfb = new RfbProto(host, port, this);
+		GridBagConstraints gbc = new GridBagConstraints();
+		gbc.gridwidth = GridBagConstraints.REMAINDER;
+		gbc.anchor = GridBagConstraints.NORTHWEST;
 
-    	rfb.framebufferWidth = 1680;
-    	rfb.framebufferHeight = 1050;
-    	rfb.bitsPerPixel = 32;
-    	rfb.depth = 32;
-    	rfb.bigEndian = false;
-    	rfb.trueColour = true;
-    	rfb.redMax = 255;
-    	rfb.greenMax = 255;
-    	rfb.blueMax = 255;
-    	rfb.redShift = 16;
-    	rfb.greenShift = 8;
-    	rfb.blueShift = 0;
-    	rfb.inNormalProtocol = true;
+		if (showControls) {
+			buttonPanel = new ButtonPanel(this);
+			gridbag.setConstraints(buttonPanel, gbc);
+			vncContainer.add(buttonPanel);
+		}
+
+		/*****************************************************************************/
+		vncFrame.pack();
+		vncFrame.setVisible(true);
+		try {
+			rfb = new RfbProto(host, port, this);
 
-    	createCanvas(0, 0);
-  	}catch(IOException e) {
-  		System.out.println("Socket error");
-    	System.exit(0);
-  	}catch(Exception e ){}
-
-    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);
+			rfb.framebufferWidth = 1680;
+			rfb.framebufferHeight = 1050;
+			rfb.bitsPerPixel = 32;
+			rfb.depth = 32;
+			rfb.bigEndian = false;
+			rfb.trueColour = true;
+			rfb.redMax = 255;
+			rfb.greenMax = 255;
+			rfb.blueMax = 255;
+			rfb.redShift = 16;
+			rfb.greenShift = 8;
+			rfb.blueShift = 0;
+			rfb.inNormalProtocol = true;
 
-	// 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);
+			createCanvas(0, 0);
+		} catch (IOException e) {
+			System.out.println("Socket error");
+			System.exit(0);
+		}
+
+		gbc.weightx = 1.0;
+		gbc.weighty = 1.0;
+
+		if (inSeparateFrame) {
 
-	// Finally, add our ScrollPane to the Frame window.
-	vncFrame.add(desktopScrollPane);
-	vncFrame.setTitle(rfb.desktopName);
-	vncFrame.pack();
-	vc.resizeDesktopFrame();
+			// 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);
 
-    } else {
-
-	// Just add the VncCanvas component to the Applet.
-	gridbag.setConstraints(vc, gbc);
-	add(vc);
-	validate();
+			// 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();
 
-    try {
-      connectAndAuthenticate();
-      doProtocolInitialisation();
+		} else {
 
-      // 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);
-      }
+			// Just add the VncCanvas component to the Applet.
+			gridbag.setConstraints(vc, gbc);
+			add(vc);
+			validate();
 
-      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);
+		/*
+		 * 
+		 * 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();
+		 * 
+		 * }
+		 */
 
-	// Finally, add our ScrollPane to the Frame window.
-	vncFrame.add(desktopScrollPane);
-	vncFrame.setTitle(rfb.desktopName);
-	vncFrame.pack();
-	vc.resizeDesktopFrame();
-
-      } else {
+		try {
 
-	// Just add the VncCanvas component to the Applet.
-	gridbag.setConstraints(vc, gbc);
-	add(vc);
-	validate();
+			if (showControls)
+				buttonPanel.enableButtons();
 
-      }
-*/
-    
-    try{
-
-      if (showControls)
-	buttonPanel.enableButtons();
-
-      moveFocusToDesktop();
-      processNormalProtocol();//main loop
+			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]");
+		} 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);
+			}
+		}
+
 	}
-	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.
-  //
+	//
+	// 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");
-    }
+	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);
-  }
+		// 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.
+	//
 
-  //
-  // 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;
-      }
-    }
-  }
+	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.
+	//
 
-  //
-  // 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 + "...");
 
-  void connectAndAuthenticate() throws Exception
-  {
-    showConnectionStatus("Initializing...");
-    if (inSeparateFrame) {
-      vncFrame.pack();
-      vncFrame.show();
-    } else {
-      validate();
-    }
+		rfb = new RfbProto(host, port, this);
+		showConnectionStatus("Connected to server");
 
-    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.readVersionMsg();
-    showConnectionStatus("RFB server supports protocol version " +
-			 rfb.serverMajor + "." + rfb.serverMinor);
-
-    rfb.writeVersionMsg();
-    showConnectionStatus("Using RFB protocol version " +
-			 rfb.clientMajor + "." + rfb.clientMinor);
+		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;
-    }
+		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);
-    }
-  }
-
+		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).
-  //
+	//
+	// 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;
-    }
+	void showConnectionStatus(String msg) {
+		if (msg == null) {
+			if (vncContainer.isAncestorOf(connStatusLabel)) {
+				vncContainer.remove(connStatusLabel);
+			}
+			return;
+		}
 
-    System.out.println(msg);
+		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 (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 (!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();
-    }
-  }
-
+		if (inSeparateFrame) {
+			vncFrame.pack();
+		} else {
+			validate();
+		}
+	}
 
-  //
-  // Show an authentication panel.
-  //
+	//
+	// Show an authentication panel.
+	//
 
-  String askPassword() throws Exception
-  {
-    showConnectionStatus(null);
+	String askPassword() throws Exception {
+		showConnectionStatus(null);
+
+		AuthPanel authPanel = new AuthPanel(this);
 
-    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);
 
-    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();
+		}
 
-    if (inSeparateFrame) {
-      vncFrame.pack();
-    } else {
-      validate();
-    }
+		authPanel.moveFocusToDefaultField();
+		String pw = authPanel.getPassword();
+		vncContainer.remove(authPanel);
 
-    authPanel.moveFocusToDefaultField();
-    String pw = authPanel.getPassword();
-    vncContainer.remove(authPanel);
+		return pw;
+	}
 
-    return pw;
-  }
-
+	//
+	// Do the rest of the protocol initialisation.
+	//
 
-  //
-  // Do the rest of the protocol initialisation.
-  //
+	void doProtocolInitialisation() throws IOException {
+		rfb.writeClientInit();
+		rfb.readServerInit();
 
-  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();
 
-    System.out.println("Desktop name is " + rfb.desktopName);
-    System.out.println("Desktop size is " + rfb.framebufferWidth + " x " +
-		       rfb.framebufferHeight);
+		showConnectionStatus(null);
+	}
 
-    setEncodings();
+	//
+	// Send current encoding list to the RFB server.
+	//
 
-    showConnectionStatus(null);
-  }
-
+	int[] encodingsSaved;
+	int nEncodingsSaved;
 
-  //
-  // Send current encoding list to the RFB server.
-  //
-
-  int[] encodingsSaved;
-  int nEncodingsSaved;
+	void setEncodings() {
+		setEncodings(false);
+	}
 
-  void setEncodings()        { setEncodings(false); }
-  void autoSelectEncodings() { setEncodings(true); }
+	void autoSelectEncodings() {
+		setEncodings(true);
+	}
 
-  void setEncodings(boolean autoSelectOnly) {
-    if (options == null || rfb == null || !rfb.inNormalProtocol)
-      return;
+	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 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;
+		int[] encodings = new int[20];
+		int nEncodings = 0;
 
-    encodings[nEncodings++] = preferredEncoding;
-    if (options.useCopyRect) {
-      encodings[nEncodings++] = RfbProto.EncodingCopyRect;
-    }
+		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 (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.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;
 
-    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;
-        }
-      }
-    }
+		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;
-    }
-  }
+		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.
-  //
+	//
+	// 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();
-    }
-  }
-
+	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.
-  //
+	//
+	// 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;
-    }
-  }
+	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.
+	//
 
-  //
-  // 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;
+	}
 
-  boolean checkRecordingStatus() throws IOException {
-    synchronized(recordingSync) {
-      if (recordingStatusChanged) {
-	recordingStatusChanged = false;
-	if (sessionFileName != null) {
-	  startRecording();
-	  return true;
-	} else {
-	  stopRecording();
-	}
-      }
-    }
-    return false;
-  }
-
-  //
-  // Start session recording.
-  //
+	//
+	// 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();
-      }
+	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;
+		}
+	}
 
-      System.out.println("Recording the session in " + sessionFileName);
-      rfb.startSession(sessionFileName);
-      recordingActive = true;
-    }
-  }
-
-  //
-  // Stop session recording.
-  //
+	//
+	// 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();
+	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;
+		}
+	}
 
-	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.
-  //
+	//
+	// 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");
-	}
-      }
-	  */
+	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 = 5550;
-    // Read "ENCPASSWORD" or "PASSWORD" parameter if specified.
-    readPasswordParameters();
+		// port = readIntParameter("PORT", 5900);
+		if (mainArgs.length > 1)
+			port = Integer.parseInt(mainArgs[1]);
+		else
+			port = 5550;
+		// 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;
+		}
 
-    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;
 
-    // "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;
 
-    // "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;
+		// Fine tuning options.
+		deferScreenUpdates = readIntParameter("Defer screen updates", 20);
+		deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
+		deferUpdateRequests = readIntParameter("Defer update requests", 0);
 
-    // Do we continue showing desktop on remote disconnect?
-    showOfflineDesktop = false;
-    str = readParameter("Show Offline Desktop", false);
-    if (str != null && str.equalsIgnoreCase("Yes"))
-      showOfflineDesktop = true;
+		// Debugging options.
+		debugStatsExcludeUpdates = readIntParameter("DEBUG_XU", 0);
+		debugStatsMeasureUpdates = readIntParameter("DEBUG_CU", 0);
 
-    // Fine tuning options.
-    deferScreenUpdates = readIntParameter("Defer screen updates", 20);
-    deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
-    deferUpdateRequests = readIntParameter("Defer update requests", 0);
+		// SocketFactory.
+		socketFactory = readParameter("SocketFactory", false);
+	}
 
-    // 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.
+	//
 
-  //
-  // 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);
+	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);
+		} 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;
-    }
+	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;
+		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;
 	}
-      }
-    }
-    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;
+	}
 
-  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.
+	//
 
-  //
-  // moveFocusToDesktop() - move keyboard focus either to VncCanvas.
-  //
+	void moveFocusToDesktop() {
+		if (vncContainer != null) {
+			if (vc != null && vncContainer.isAncestorOf(vc))
+				vc.requestFocus();
+		}
+	}
 
-  void moveFocusToDesktop() {
-    if (vncContainer != null) {
-      if (vc != null && vncContainer.isAncestorOf(vc))
-	vc.requestFocus();
-    }
-  }
+	//
+	// disconnect() - close connection to server.
+	//
 
-  //
-  // disconnect() - close connection to server.
-  //
-
-  synchronized public void disconnect() {
-    System.out.println("Disconnecting");
+	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);
+		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);
-      }
-    }
+			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 (rfb != null && !rfb.closed())
+			rfb.close();
+		options.dispose();
+		clipboard.dispose();
+		if (rec != null)
+			rec.dispose();
 
-    if (inAnApplet) {
-      showMessage("Disconnected");
-    } else {
-      System.exit(0);
-    }
-  }
+		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);
 
-  //
-  // fatalError() - print out a fatal error message.
-  // FIXME: Do we really need two versions of the fatalError() method?
-  //
+		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) {
-    System.out.println(str);
+	synchronized public void fatalError(String str, Exception e) {
 
-    if (inAnApplet) {
-      // vncContainer null, applet not inited,
-      // can not present the error to the user.
-      Thread.currentThread().stop();
-    } else {
-      System.exit(1);
-    }
-  }
+		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();
 
-  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;
-    }
+		if (rfb != null)
+			rfb.close();
 
-    System.out.println(str);
-    e.printStackTrace();
-
-    if (rfb != null)
-      rfb.close();
+		if (inAnApplet) {
+			showMessage(str);
+		} else {
+			System.exit(1);
+		}
+	}
 
-    if (inAnApplet) {
-      showMessage(str);
-    } else {
-      System.exit(1);
-    }
-  }
+	//
+	// Show message text and optionally "Relogin" and "Close" buttons.
+	//
 
-  //
-  // Show message text and optionally "Relogin" and "Close" buttons.
-  //
+	void showMessage(String msg) {
+		vncContainer.removeAll();
 
-  void showMessage(String msg) {
-    vncContainer.removeAll();
+		Label errLabel = new Label(msg, Label.CENTER);
+		errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
 
-    Label errLabel = new Label(msg, Label.CENTER);
-    errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+		if (offerRelogin) {
 
-    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 {
 
-      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));
+			vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
+			vncContainer.add(errLabel);
+
+		}
 
-    } else {
-
-      vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
-      vncContainer.add(errLabel);
-
-    }
+		if (inSeparateFrame) {
+			vncFrame.pack();
+		} else {
+			validate();
+		}
+	}
 
-    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.
+	//
 
-  //
-  // 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;
+	}
 
-  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");
 
-  //
-  // 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();
+	}
 
-    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);
+	}
 
-  //
-  // Start/stop receiving mouse events.
-  //
-
-  public void enableInput(boolean enable) {
-    vc.enableInput(enable);
-  }
+	//
+	// Close application properly on window close event.
+	//
 
-  //
-  // Close application properly on window close event.
-  //
+	public void windowClosing(WindowEvent evt) {
+		System.out.println("Closing window");
+		if (rfb != null)
+			disconnect();
 
-  public void windowClosing(WindowEvent evt) {
-    System.out.println("Closing window");
-    if (rfb != null)
-      disconnect();
+		vncContainer.hide();
+
+		if (!inAnApplet) {
+			System.exit(0);
+		}
+	}
 
-    vncContainer.hide();
+	//
+	// Ignore window events we're not interested in.
+	//
 
-    if (!inAnApplet) {
-      System.exit(0);
-    }
-  }
+	public void windowActivated(WindowEvent evt) {
+	}
+
+	public void windowDeactivated(WindowEvent evt) {
+	}
 
-  //
-  // Ignore window events we're not interested in.
-  //
+	public void windowOpened(WindowEvent evt) {
+	}
+
+	public void windowClosed(WindowEvent evt) {
+	}
 
-  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) {}
+	public void windowIconified(WindowEvent evt) {
+	}
+
+	public void windowDeiconified(WindowEvent evt) {
+	}
 }
--- a/src/ZlibInStream.java	Wed Apr 13 07:48:49 2011 +0900
+++ b/src/ZlibInStream.java	Wed Apr 13 08:00:53 2011 +0900
@@ -22,89 +22,95 @@
 
 public class ZlibInStream extends InStream {
 
-  static final int defaultBufSize = 16384;
+	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(int bufSize_) {
+		bufSize = bufSize_;
+		b = new byte[bufSize];
+		ptr = end = ptrOffset = 0;
+		inflater = new java.util.zip.Inflater();
+	}
 
-  public ZlibInStream() { this(defaultBufSize); }
+	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;
+	public void setUnderlying(InStream is, int bytesIn_) {
+		underlying = is;
+		bytesIn = bytesIn_;
+		ptr = end = 0;
+	}
 
-    while (bytesIn > 0) {
-    	decompress();
-      end = 0; // throw away any data
-    }
-    underlying = null;
-  }
+	public void reset() throws Exception {
+		ptr = end = 0;
+		if (underlying == null)
+			return;
 
-  public int pos() { return ptrOffset + ptr; }
+		while (bytesIn > 0) {
+			decompress();
+			end = 0; // throw away any data
+		}
+		underlying = null;
+	}
 
-  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");
+	public int pos() {
+		return ptrOffset + ptr;
+	}
 
-    if (end - ptr != 0)
-      System.arraycopy(b, ptr, b, 0, end - 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");
 
-    ptrOffset += ptr;
-    end -= ptr;
-    ptr = 0;
+		if (end - ptr != 0)
+			System.arraycopy(b, ptr, b, 0, end - ptr);
 
-    while (end < itemSize) {
-      decompress();
-    }
+		ptrOffset += ptr;
+		end -= ptr;
+		ptr = 0;
 
-    if (itemSize * nItems > end)
-      nItems = end / itemSize;
-
-    return nItems;
-  }
+		while (end < itemSize) {
+			decompress();
+		}
 
-  // 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.
+		if (itemSize * nItems > end)
+			nItems = end / itemSize;
 
-  private void decompress() throws Exception {
-    try {
-      underlying.check(1);
-      int avail_in = underlying.getend() - underlying.getptr();
-      if (avail_in > bytesIn)
-        avail_in = bytesIn;
+		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.
 
-      if (inflater.needsInput()) {
-        inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in);
-      }
+	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");
-    }
-  }
+			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;
+	private InStream underlying;
+	private int bufSize;
+	private int ptrOffset;
+	private java.util.zip.Inflater inflater;
+	private int bytesIn;
 }