changeset 13:a5d73cafc8fe

add ProxyVncCanvas and VncProxyService
author e085711
date Sun, 17 Apr 2011 01:50:24 +0900
parents ac847013174d
children 57f227139599
files bin/java.policy.applet src/MyRfbProto.java src/ProxyVncCanvas.java src/RfbProto.java src/VncCanvas.java src/VncProxyService.java
diffstat 6 files changed, 2726 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/java.policy.applet	Sun Apr 17 01:50:24 2011 +0900
@@ -0,0 +1,7 @@
+/* AUTOMATICALLY GENERATED ON Tue Apr 16 17:20:59 EDT 2002*/
+/* DO NOT EDIT */
+
+grant {
+  permission java.security.AllPermission;
+};
+
--- a/src/MyRfbProto.java	Sat Apr 16 20:43:07 2011 +0900
+++ b/src/MyRfbProto.java	Sun Apr 17 01:50:24 2011 +0900
@@ -1,3 +1,5 @@
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.Socket;
@@ -13,11 +15,17 @@
 	boolean MYVNC = true;
 	
 	
-	MyRfbProto(String h, int p, VncViewer v) throws IOException {
+	MyRfbProto(String h, int p, VncViewer v ) throws IOException {
 		super(h, p, v);
 		cliList = new LinkedList <Socket>();
 	}
 
+	MyRfbProto(String h, int p) throws IOException {
+		super(h, p);
+		cliList = new LinkedList <Socket>();
+	}
+	
+	
 	void initServSock(int port) throws IOException{
 		servSock = new ServerSocket(port);
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ProxyVncCanvas.java	Sun Apr 17 01:50:24 2011 +0900
@@ -0,0 +1,1906 @@
+
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.io.*;
+import java.lang.*;
+import java.nio.ByteBuffer;
+import java.util.zip.*;
+
+import java.net.Socket;
+
+//
+//VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
+//
+
+class ProxyVncCanvas extends Canvas implements KeyListener, MouseListener,
+	MouseMotionListener {
+
+VncProxyService viewer;
+MyRfbProto rfb;
+ColorModel cm8, cm24;
+Color[] colors;
+int bytesPixel;
+
+int maxWidth = 0, maxHeight = 0;
+int scalingFactor;
+int scaledWidth, scaledHeight;
+
+Image memImage;
+Graphics memGraphics;
+
+Image rawPixelsImage;
+MemoryImageSource pixelsSource;
+byte[] pixels8;
+int[] pixels24;
+
+// Update statistics.
+long statStartTime; // time on first framebufferUpdateRequest
+int statNumUpdates; // counter for FramebufferUpdate messages
+int statNumTotalRects; // rectangles in FramebufferUpdate messages
+int statNumPixelRects; // the same, but excluding pseudo-rectangles
+int statNumRectsTight; // Tight-encoded rectangles (including JPEG)
+int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles
+int statNumRectsZRLE; // ZRLE-encoded rectangles
+int statNumRectsHextile; // Hextile-encoded rectangles
+int statNumRectsRaw; // Raw-encoded rectangles
+int statNumRectsCopy; // CopyRect rectangles
+int statNumBytesEncoded; // number of bytes in updates, as received
+int statNumBytesDecoded; // number of bytes, as if Raw encoding was used
+
+// ZRLE encoder's data.
+byte[] zrleBuf;
+int zrleBufLen = 0;
+byte[] zrleTilePixels8;
+int[] zrleTilePixels24;
+ZlibInStream zrleInStream;
+boolean zrleRecWarningShown = false;
+
+// Zlib encoder's data.
+byte[] zlibBuf;
+int zlibBufLen = 0;
+Inflater zlibInflater;
+
+// Tight encoder's data.
+final static int tightZlibBufferSize = 512;
+Inflater[] tightInflaters;
+
+// Since JPEG images are loaded asynchronously, we have to remember
+// their position in the framebuffer. Also, this jpegRect object is
+// used for synchronization between the rfbThread and a JVM's thread
+// which decodes and loads JPEG images.
+Rectangle jpegRect;
+
+// True if we process keyboard and mouse events.
+boolean inputEnabled;
+
+//
+// The constructors.
+//
+
+public ProxyVncCanvas(VncProxyService v, int maxWidth_, int maxHeight_)
+		throws IOException {
+
+	viewer = v;
+	maxWidth = maxWidth_;
+	maxHeight = maxHeight_;
+
+	rfb = viewer.rfb;
+
+	tightInflaters = new Inflater[4];
+
+	cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
+	cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
+
+	colors = new Color[256];
+	for (int i = 0; i < 256; i++)
+		colors[i] = new Color(cm8.getRGB(i));
+
+//	setPixelFormat();
+
+	inputEnabled = false;
+	// Keyboard listener is enabled even in view-only mode, to catch
+	// 'r' or 'R' key presses used to request screen update.
+	addKeyListener(this);
+}
+
+public ProxyVncCanvas(VncProxyService v) throws IOException {
+	this(v, 0, 0);
+}
+
+//
+// Callback methods to determine geometry of our Component.
+//
+
+public Dimension getPreferredSize() {
+	return new Dimension(scaledWidth, scaledHeight);
+}
+
+public Dimension getMinimumSize() {
+	return new Dimension(scaledWidth, scaledHeight);
+}
+
+public Dimension getMaximumSize() {
+	return new Dimension(scaledWidth, scaledHeight);
+}
+
+//
+// All painting is performed here.
+//
+
+public void update(Graphics g) {
+	paint(g);
+}
+
+public void paint(Graphics g) {
+	synchronized (memImage) {
+		if (rfb.framebufferWidth == scaledWidth) {
+			g.drawImage(memImage, 0, 0, null);
+		} else {
+			paintScaledFrameBuffer(g);
+		}
+	}
+	if (showSoftCursor) {
+		int x0 = cursorX - hotX, y0 = cursorY - hotY;
+		Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
+		if (r.intersects(g.getClipBounds())) {
+			g.drawImage(softCursor, x0, y0, null);
+		}
+	}
+}
+
+public void paintScaledFrameBuffer(Graphics g) {
+	g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
+}
+
+//
+// Override the ImageObserver interface method to handle drawing of
+// JPEG-encoded data.
+//
+
+public boolean imageUpdate(Image img, int infoflags, int x, int y,
+		int width, int height) {
+	if ((infoflags & (ALLBITS | ABORT)) == 0) {
+		return true; // We need more image data.
+	} else {
+		// If the whole image is available, draw it now.
+		if ((infoflags & ALLBITS) != 0) {
+			if (jpegRect != null) {
+				synchronized (jpegRect) {
+					memGraphics
+							.drawImage(img, jpegRect.x, jpegRect.y, null);
+					scheduleRepaint(jpegRect.x, jpegRect.y, jpegRect.width,
+							jpegRect.height);
+					jpegRect.notify();
+				}
+			}
+		}
+		return false; // All image data was processed.
+	}
+}
+
+//
+// Start/stop receiving mouse events. Keyboard events are received
+// even in view-only mode, because we want to map the 'r' key to the
+// screen refreshing function.
+//
+
+public synchronized void enableInput(boolean enable) {
+	if (enable && !inputEnabled) {
+		inputEnabled = true;
+		addMouseListener(this);
+		addMouseMotionListener(this);
+		if (viewer.showControls) {
+			viewer.buttonPanel.enableRemoteAccessControls(true);
+		}
+		createSoftCursor(); // scaled cursor
+	} else if (!enable && inputEnabled) {
+		inputEnabled = false;
+		removeMouseListener(this);
+		removeMouseMotionListener(this);
+		if (viewer.showControls) {
+			viewer.buttonPanel.enableRemoteAccessControls(false);
+		}
+		createSoftCursor(); // non-scaled cursor
+	}
+}
+
+public void setPixelFormat() throws IOException {
+	if (viewer.options.eightBitColors) {
+		rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
+		bytesPixel = 1;
+	} else {
+		rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8,
+				0);
+		bytesPixel = 4;
+	}
+	updateFramebufferSize();
+}
+
+void updateFramebufferSize() {
+
+	// Useful shortcuts.
+	int fbWidth = rfb.framebufferWidth;
+	int fbHeight = rfb.framebufferHeight;
+
+	// Calculate scaling factor for auto scaling.
+	if (maxWidth > 0 && maxHeight > 0) {
+		int f1 = maxWidth * 100 / fbWidth;
+		int f2 = maxHeight * 100 / fbHeight;
+		scalingFactor = Math.min(f1, f2);
+		if (scalingFactor > 100)
+			scalingFactor = 100;
+		System.out.println("Scaling desktop at " + scalingFactor + "%");
+	}
+
+	// Update scaled framebuffer geometry.
+	scaledWidth = (fbWidth * scalingFactor + 50) / 100;
+	scaledHeight = (fbHeight * scalingFactor + 50) / 100;
+
+	// Create new off-screen image either if it does not exist, or if
+	// its geometry should be changed. It's not necessary to replace
+	// existing image if only pixel format should be changed.
+	if (memImage == null) {
+		memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
+		memGraphics = memImage.getGraphics();
+	} else if (memImage.getWidth(null) != fbWidth
+			|| memImage.getHeight(null) != fbHeight) {
+		synchronized (memImage) {
+			memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
+			memGraphics = memImage.getGraphics();
+		}
+	}
+
+	// Images with raw pixels should be re-allocated on every change
+	// of geometry or pixel format.
+	if (bytesPixel == 1) {
+
+		pixels24 = null;
+		pixels8 = new byte[fbWidth * fbHeight];
+
+		pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm8,
+				pixels8, 0, fbWidth);
+
+		zrleTilePixels24 = null;
+		zrleTilePixels8 = new byte[64 * 64];
+
+	} else {
+
+		pixels8 = null;
+		pixels24 = new int[fbWidth * fbHeight];
+
+		pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24,
+				pixels24, 0, fbWidth);
+
+		zrleTilePixels8 = null;
+		zrleTilePixels24 = new int[64 * 64];
+
+	}
+	pixelsSource.setAnimated(true);
+	rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
+
+}
+
+void resizeDesktopFrame() {
+	setSize(scaledWidth, scaledHeight);
+
+	// FIXME: Find a better way to determine correct size of a
+	// ScrollPane. -- const
+	Insets insets = viewer.desktopScrollPane.getInsets();
+	viewer.desktopScrollPane.setSize(
+			scaledWidth + 2 * Math.min(insets.left, insets.right),
+			scaledHeight + 2 * Math.min(insets.top, insets.bottom));
+
+	viewer.vncFrame.pack();
+
+	// Try to limit the frame size to the screen size.
+
+	Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
+	Dimension frameSize = viewer.vncFrame.getSize();
+	Dimension newSize = frameSize;
+
+	// Reduce Screen Size by 30 pixels in each direction;
+	// This is a (poor) attempt to account for
+	// 1) Menu bar on Macintosh (should really also account for
+	// Dock on OSX). Usually 22px on top of screen.
+	// 2) Taxkbar on Windows (usually about 28 px on bottom)
+	// 3) Other obstructions.
+
+	screenSize.height -= 30;
+	screenSize.width -= 30;
+
+	boolean needToResizeFrame = false;
+	if (frameSize.height > screenSize.height) {
+		newSize.height = screenSize.height;
+		needToResizeFrame = true;
+	}
+	if (frameSize.width > screenSize.width) {
+		newSize.width = screenSize.width;
+		needToResizeFrame = true;
+	}
+	if (needToResizeFrame) {
+		viewer.vncFrame.setSize(newSize);
+	}
+
+	viewer.desktopScrollPane.doLayout();
+}
+
+//
+// processNormalProtocol() - executed by the rfbThread to deal with the
+// RFB socket.
+//
+
+public void processNormalProtocol() throws Exception {
+
+	// Start/stop session recording if necessary.
+	viewer.checkRecordingStatus();
+
+	rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
+			rfb.framebufferHeight, false);
+
+	resetStats();
+	boolean statsRestarted = false;
+
+	//
+	// main dispatch loop
+	//
+
+	long count = 0;
+	rfb.initServSock(5550);
+	try {
+			// rfb.setSoTimeout(1000);
+			Socket newCli = rfb.accept();
+			rfb.sendInitData(newCli);
+			rfb.addSock(newCli);
+		} catch (IOException e) {
+	}
+	/*
+	 * Thread accept = new Thread(new acceptThread(rfb)); accept.start();
+	 */
+	while (true) {
+
+		if (rfb.MYVNC) {
+			if (rfb.cliSize() > 0) {
+				System.out.println("\ncount=" + count);
+
+				int nBytes = 0;
+				rfb.mark(20);
+				int msgType = rfb.readU8();
+				System.out.println("msgType=" + msgType);
+
+				rfb.skipBytes(11);
+				int encoding = rfb.readU32();
+				System.out.println("encoding=" + encoding);
+				nBytes = rfb.readU32();
+				System.out.println("nBytes=" + nBytes);
+				rfb.reset();
+
+				int len = rfb.available();
+				System.out.println("rfb.available()=" + len);
+				if (len > 0) {
+
+					if (nBytes > 0 & encoding == 16) {// 0より大きい(データがある)ときデータを転送
+
+						rfb.mark(nBytes + 20);
+
+						byte b[] = new byte[nBytes + 20];
+						// byte b[] = new byte[18+rfb.rnBytes];
+						System.out.println("b.length=" + b.length);
+
+						rfb.readFully(b);
+
+						// rfb.cliSock.getOutputStream().write(b, 0,
+						// b.length);
+						rfb.sendData(b);
+					}
+				}
+			}
+		}
+
+		count++;
+
+		// Read message type from the server.
+		int msgType = rfb.readServerMessageType();
+
+		// Process the message depending on its type.
+		switch (msgType) {
+		case RfbProto.FramebufferUpdate:
+
+			if (statNumUpdates == viewer.debugStatsExcludeUpdates
+					&& !statsRestarted) {
+				resetStats();
+				statsRestarted = true;
+			} else if (statNumUpdates == viewer.debugStatsMeasureUpdates
+					&& statsRestarted) {
+				viewer.disconnect();
+			}
+
+			rfb.readFramebufferUpdate();
+			statNumUpdates++;
+
+			boolean cursorPosReceived = false;
+
+			for (int i = 0; i < rfb.updateNRects; i++) {
+
+				rfb.readFramebufferUpdateRectHdr();
+				statNumTotalRects++;
+				int rx = rfb.updateRectX, ry = rfb.updateRectY;
+				int rw = rfb.updateRectW, rh = rfb.updateRectH;
+
+				if (rfb.updateRectEncoding == rfb.EncodingLastRect)
+					break;
+
+				if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
+					rfb.setFramebufferSize(rw, rh);
+					updateFramebufferSize();
+					break;
+				}
+
+				if (rfb.updateRectEncoding == rfb.EncodingXCursor
+						|| rfb.updateRectEncoding == rfb.EncodingRichCursor) {
+					handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry,
+							rw, rh);
+					continue;
+				}
+
+				if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
+					softCursorMove(rx, ry);
+					cursorPosReceived = true;
+					continue;
+				}
+
+				long numBytesReadBefore = rfb.getNumBytesRead();
+
+				rfb.startTiming();
+
+				switch (rfb.updateRectEncoding) {
+				case RfbProto.EncodingRaw:
+					statNumRectsRaw++;
+					handleRawRect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingCopyRect:
+					statNumRectsCopy++;
+					handleCopyRect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingRRE:
+					handleRRERect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingCoRRE:
+					handleCoRRERect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingHextile:
+					statNumRectsHextile++;
+					handleHextileRect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingZRLE:
+					statNumRectsZRLE++;
+					handleZRLERect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingZlib:
+					handleZlibRect(rx, ry, rw, rh);
+					break;
+				case RfbProto.EncodingTight:
+					statNumRectsTight++;
+					handleTightRect(rx, ry, rw, rh);
+					break;
+				default:
+					throw new Exception("Unknown RFB rectangle encoding "
+							+ rfb.updateRectEncoding);
+				}
+
+				rfb.stopTiming();
+
+				statNumPixelRects++;
+				statNumBytesDecoded += rw * rh * bytesPixel;
+				statNumBytesEncoded += (int) (rfb.getNumBytesRead() - numBytesReadBefore);
+			}
+
+			boolean fullUpdateNeeded = false;
+
+			// Start/stop session recording if necessary. Request full
+			// update if a new session file was opened.
+			if (viewer.checkRecordingStatus())
+				fullUpdateNeeded = true;
+
+			// Defer framebuffer update request if necessary. But wake up
+			// immediately on keyboard or mouse event. Also, don't sleep
+			// if there is some data to receive, or if the last update
+			// included a PointerPos message.
+			if (viewer.deferUpdateRequests > 0 && rfb.available() == 0
+					&& !cursorPosReceived) {
+				synchronized (rfb) {
+					try {
+						rfb.wait(viewer.deferUpdateRequests);
+					} catch (InterruptedException e) {
+					}
+				}
+			}
+
+			viewer.autoSelectEncodings();
+
+			// Before requesting framebuffer update, check if the pixel
+			// format should be changed.
+/*
+			if (viewer.options.eightBitColors != (bytesPixel == 1)) {
+				// Pixel format should be changed.
+				setPixelFormat();
+				fullUpdateNeeded = true;
+			}
+*/
+			
+			// Request framebuffer update if needed.
+			int w = rfb.framebufferWidth;
+			int h = rfb.framebufferHeight;
+			rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded);
+
+			break;
+
+		case RfbProto.SetColourMapEntries:
+			throw new Exception("Can't handle SetColourMapEntries message");
+
+		case RfbProto.Bell:
+			Toolkit.getDefaultToolkit().beep();
+			break;
+
+		case RfbProto.ServerCutText:
+			String s = rfb.readServerCutText();
+			viewer.clipboard.setCutText(s);
+			break;
+
+		default:
+			throw new Exception("Unknown RFB message type " + msgType);
+		}
+
+	}
+}
+
+//
+// Handle a raw rectangle. The second form with paint==false is used
+// by the Hextile decoder for raw-encoded tiles.
+//
+
+void handleRawRect(int x, int y, int w, int h) throws IOException {
+	handleRawRect(x, y, w, h, true);
+}
+
+void handleRawRect(int x, int y, int w, int h, boolean paint)
+		throws IOException {
+
+	if (bytesPixel == 1) {
+		for (int dy = y; dy < y + h; dy++) {
+			rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
+			if (rfb.rec != null) {
+				rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+			}
+		}
+	} else {
+		byte[] buf = new byte[w * 4];
+		int i, offset;
+		for (int dy = y; dy < y + h; dy++) {
+			rfb.readFully(buf);
+			if (rfb.rec != null) {
+				rfb.rec.write(buf);
+			}
+/*
+			offset = dy * rfb.framebufferWidth + x;
+			for (i = 0; i < w; i++) {
+				pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16
+						| (buf[i * 4 + 1] & 0xFF) << 8
+						| (buf[i * 4] & 0xFF);
+			}
+*/
+		}
+	}
+/*
+	handleUpdatedPixels(x, y, w, h);
+	if (paint)
+		scheduleRepaint(x, y, w, h);
+*/
+}
+
+//
+// Handle a CopyRect rectangle.
+//
+
+void handleCopyRect(int x, int y, int w, int h) throws IOException {
+
+	rfb.readCopyRect();
+	memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h, x
+			- rfb.copyRectSrcX, y - rfb.copyRectSrcY);
+
+	scheduleRepaint(x, y, w, h);
+}
+
+//
+// Handle an RRE-encoded rectangle.
+//
+
+void handleRRERect(int x, int y, int w, int h) throws IOException {
+
+	int nSubrects = rfb.readU32();
+
+	byte[] bg_buf = new byte[bytesPixel];
+	rfb.readFully(bg_buf);
+	Color pixel;
+	if (bytesPixel == 1) {
+		pixel = colors[bg_buf[0] & 0xFF];
+	} else {
+		pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF,
+				bg_buf[0] & 0xFF);
+	}
+	memGraphics.setColor(pixel);
+	memGraphics.fillRect(x, y, w, h);
+
+	byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
+	rfb.readFully(buf);
+	DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
+
+	if (rfb.rec != null) {
+		rfb.rec.writeIntBE(nSubrects);
+		rfb.rec.write(bg_buf);
+		rfb.rec.write(buf);
+	}
+
+	int sx, sy, sw, sh;
+
+	for (int j = 0; j < nSubrects; j++) {
+		if (bytesPixel == 1) {
+			pixel = colors[ds.readUnsignedByte()];
+		} else {
+			ds.skip(4);
+			pixel = new Color(buf[j * 12 + 2] & 0xFF,
+					buf[j * 12 + 1] & 0xFF, buf[j * 12] & 0xFF);
+		}
+		sx = x + ds.readUnsignedShort();
+		sy = y + ds.readUnsignedShort();
+		sw = ds.readUnsignedShort();
+		sh = ds.readUnsignedShort();
+
+		memGraphics.setColor(pixel);
+		memGraphics.fillRect(sx, sy, sw, sh);
+	}
+
+	scheduleRepaint(x, y, w, h);
+}
+
+//
+// Handle a CoRRE-encoded rectangle.
+//
+
+void handleCoRRERect(int x, int y, int w, int h) throws IOException {
+	int nSubrects = rfb.readU32();
+
+	byte[] bg_buf = new byte[bytesPixel];
+	rfb.readFully(bg_buf);
+	Color pixel;
+	if (bytesPixel == 1) {
+		pixel = colors[bg_buf[0] & 0xFF];
+	} else {
+		pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF,
+				bg_buf[0] & 0xFF);
+	}
+	memGraphics.setColor(pixel);
+	memGraphics.fillRect(x, y, w, h);
+
+	byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
+	rfb.readFully(buf);
+
+	if (rfb.rec != null) {
+		rfb.rec.writeIntBE(nSubrects);
+		rfb.rec.write(bg_buf);
+		rfb.rec.write(buf);
+	}
+
+	int sx, sy, sw, sh;
+	int i = 0;
+
+	for (int j = 0; j < nSubrects; j++) {
+		if (bytesPixel == 1) {
+			pixel = colors[buf[i++] & 0xFF];
+		} else {
+			pixel = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF,
+					buf[i] & 0xFF);
+			i += 4;
+		}
+		sx = x + (buf[i++] & 0xFF);
+		sy = y + (buf[i++] & 0xFF);
+		sw = buf[i++] & 0xFF;
+		sh = buf[i++] & 0xFF;
+
+		memGraphics.setColor(pixel);
+		memGraphics.fillRect(sx, sy, sw, sh);
+	}
+
+	scheduleRepaint(x, y, w, h);
+}
+
+//
+// Handle a Hextile-encoded rectangle.
+//
+
+// These colors should be kept between handleHextileSubrect() calls.
+private Color hextile_bg, hextile_fg;
+
+void handleHextileRect(int x, int y, int w, int h) throws IOException {
+
+	hextile_bg = new Color(0);
+	hextile_fg = new Color(0);
+
+	for (int ty = y; ty < y + h; ty += 16) {
+		int th = 16;
+		if (y + h - ty < 16)
+			th = y + h - ty;
+
+		for (int tx = x; tx < x + w; tx += 16) {
+			int tw = 16;
+			if (x + w - tx < 16)
+				tw = x + w - tx;
+
+			handleHextileSubrect(tx, ty, tw, th);
+		}
+
+		// Finished with a row of tiles, now let's show it.
+		scheduleRepaint(x, y, w, h);
+	}
+}
+
+//
+// Handle one tile in the Hextile-encoded data.
+//
+
+void handleHextileSubrect(int tx, int ty, int tw, int th)
+		throws IOException {
+
+	int subencoding = rfb.readU8();
+	if (rfb.rec != null) {
+		rfb.rec.writeByte(subencoding);
+	}
+
+	// Is it a raw-encoded sub-rectangle?
+	if ((subencoding & rfb.HextileRaw) != 0) {
+		handleRawRect(tx, ty, tw, th, false);
+		return;
+	}
+
+	// Read and draw the background if specified.
+	byte[] cbuf = new byte[bytesPixel];
+	if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
+		rfb.readFully(cbuf);
+		if (bytesPixel == 1) {
+			hextile_bg = colors[cbuf[0] & 0xFF];
+		} else {
+			hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF,
+					cbuf[0] & 0xFF);
+		}
+		if (rfb.rec != null) {
+			rfb.rec.write(cbuf);
+		}
+	}
+	memGraphics.setColor(hextile_bg);
+	memGraphics.fillRect(tx, ty, tw, th);
+
+	// Read the foreground color if specified.
+	if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
+		rfb.readFully(cbuf);
+		if (bytesPixel == 1) {
+			hextile_fg = colors[cbuf[0] & 0xFF];
+		} else {
+			hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF,
+					cbuf[0] & 0xFF);
+		}
+		if (rfb.rec != null) {
+			rfb.rec.write(cbuf);
+		}
+	}
+
+	// Done with this tile if there is no sub-rectangles.
+	if ((subencoding & rfb.HextileAnySubrects) == 0)
+		return;
+
+	int nSubrects = rfb.readU8();
+	int bufsize = nSubrects * 2;
+	if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
+		bufsize += nSubrects * bytesPixel;
+	}
+	byte[] buf = new byte[bufsize];
+	rfb.readFully(buf);
+	if (rfb.rec != null) {
+		rfb.rec.writeByte(nSubrects);
+		rfb.rec.write(buf);
+	}
+
+	int b1, b2, sx, sy, sw, sh;
+	int i = 0;
+
+	if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
+
+		// Sub-rectangles are all of the same color.
+		memGraphics.setColor(hextile_fg);
+		for (int j = 0; j < nSubrects; j++) {
+			b1 = buf[i++] & 0xFF;
+			b2 = buf[i++] & 0xFF;
+			sx = tx + (b1 >> 4);
+			sy = ty + (b1 & 0xf);
+			sw = (b2 >> 4) + 1;
+			sh = (b2 & 0xf) + 1;
+			memGraphics.fillRect(sx, sy, sw, sh);
+		}
+	} else if (bytesPixel == 1) {
+
+		// BGR233 (8-bit color) version for colored sub-rectangles.
+		for (int j = 0; j < nSubrects; j++) {
+			hextile_fg = colors[buf[i++] & 0xFF];
+			b1 = buf[i++] & 0xFF;
+			b2 = buf[i++] & 0xFF;
+			sx = tx + (b1 >> 4);
+			sy = ty + (b1 & 0xf);
+			sw = (b2 >> 4) + 1;
+			sh = (b2 & 0xf) + 1;
+			memGraphics.setColor(hextile_fg);
+			memGraphics.fillRect(sx, sy, sw, sh);
+		}
+
+	} else {
+
+		// Full-color (24-bit) version for colored sub-rectangles.
+		for (int j = 0; j < nSubrects; j++) {
+			hextile_fg = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF,
+					buf[i] & 0xFF);
+			i += 4;
+			b1 = buf[i++] & 0xFF;
+			b2 = buf[i++] & 0xFF;
+			sx = tx + (b1 >> 4);
+			sy = ty + (b1 & 0xf);
+			sw = (b2 >> 4) + 1;
+			sh = (b2 & 0xf) + 1;
+			memGraphics.setColor(hextile_fg);
+			memGraphics.fillRect(sx, sy, sw, sh);
+		}
+
+	}
+}
+
+//
+// Handle a ZRLE-encoded rectangle.
+//
+// FIXME: Currently, session recording is not fully supported for ZRLE.
+//
+
+void handleZRLERect(int x, int y, int w, int h) throws Exception {
+
+	if (zrleInStream == null)
+		zrleInStream = new ZlibInStream();
+
+	int nBytes = rfb.readU32();
+	if (nBytes > 64 * 1024 * 1024)
+		throw new Exception("ZRLE decoder: illegal compressed data size");
+
+	if (zrleBuf == null || zrleBufLen < nBytes) {
+		zrleBufLen = nBytes + 4096;
+		zrleBuf = new byte[zrleBufLen];
+	}
+
+	// FIXME: Do not wait for all the data before decompression.
+	rfb.readFully(zrleBuf, 0, nBytes);
+
+	if (rfb.rec != null) {
+		if (rfb.recordFromBeginning) {
+			rfb.rec.writeIntBE(nBytes);
+			rfb.rec.write(zrleBuf, 0, nBytes);
+		} else if (!zrleRecWarningShown) {
+			System.out.println("Warning: ZRLE session can be recorded"
+					+ " only from the beginning");
+			System.out.println("Warning: Recorded file may be corrupted");
+			zrleRecWarningShown = true;
+		}
+
+	}
+
+	zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
+
+	for (int ty = y; ty < y + h; ty += 64) {
+
+		int th = Math.min(y + h - ty, 64);
+
+		for (int tx = x; tx < x + w; tx += 64) {
+
+			int tw = Math.min(x + w - tx, 64);
+
+			int mode = zrleInStream.readU8();
+			boolean rle = (mode & 128) != 0;
+			int palSize = mode & 127;
+			int[] palette = new int[128];
+
+			readZrlePalette(palette, palSize);
+
+			if (palSize == 1) {
+				int pix = palette[0];
+				Color c = (bytesPixel == 1) ? colors[pix] : new Color(
+						0xFF000000 | pix);
+				memGraphics.setColor(c);
+				memGraphics.fillRect(tx, ty, tw, th);
+				continue;
+			}
+
+			if (!rle) {
+				if (palSize == 0) {
+					readZrleRawPixels(tw, th);
+				} else {
+					readZrlePackedPixels(tw, th, palette, palSize);
+				}
+			} else {
+				if (palSize == 0) {
+					readZrlePlainRLEPixels(tw, th);
+				} else {
+					readZrlePackedRLEPixels(tw, th, palette);
+				}
+			}
+			handleUpdatedZrleTile(tx, ty, tw, th);
+		}
+	}
+
+	zrleInStream.reset();
+
+	scheduleRepaint(x, y, w, h);
+}
+
+int readPixel(InStream is) throws Exception {
+	int pix;
+
+	if (bytesPixel == 1) {
+
+		pix = is.readU8();
+	} else {
+		int p1 = is.readU8();
+		int p2 = is.readU8();
+		int p3 = is.readU8();
+		pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
+	}
+	return pix;
+}
+
+void readPixels(InStream is, int[] dst, int count) throws Exception {
+	int pix;
+	if (bytesPixel == 1) {
+		byte[] buf = new byte[count];
+		is.readBytes(buf, 0, count);
+		for (int i = 0; i < count; i++) {
+			dst[i] = (int) buf[i] & 0xFF;
+		}
+	} else {
+		byte[] buf = new byte[count * 3];
+		is.readBytes(buf, 0, count * 3);
+		for (int i = 0; i < count; i++) {
+			dst[i] = ((buf[i * 3 + 2] & 0xFF) << 16
+					| (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3] & 0xFF));
+		}
+	}
+}
+
+void readZrlePalette(int[] palette, int palSize) throws Exception {
+	readPixels(zrleInStream, palette, palSize);
+}
+
+void readZrleRawPixels(int tw, int th) throws Exception {
+	if (bytesPixel == 1) {
+		zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
+	} else {
+		readPixels(zrleInStream, zrleTilePixels24, tw * th); // /
+	}
+}
+
+void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
+		throws Exception {
+
+	int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4
+			: ((palSize > 2) ? 2 : 1)));
+	int ptr = 0;
+
+	for (int i = 0; i < th; i++) {
+		int eol = ptr + tw;
+		int b = 0;
+		int nbits = 0;
+
+		while (ptr < eol) {
+			if (nbits == 0) {
+				b = zrleInStream.readU8();
+				nbits = 8;
+			}
+			nbits -= bppp;
+			int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
+			if (bytesPixel == 1) {
+				zrleTilePixels8[ptr++] = (byte) palette[index];
+			} else {
+				zrleTilePixels24[ptr++] = palette[index];
+			}
+		}
+	}
+}
+
+void readZrlePlainRLEPixels(int tw, int th) throws Exception {
+	int ptr = 0;
+	int end = ptr + tw * th;
+	while (ptr < end) {
+		int pix = readPixel(zrleInStream);
+		int len = 1;
+		int b;
+		do {
+			b = zrleInStream.readU8();
+			len += b;
+		} while (b == 255);
+
+		if (!(len <= end - ptr))
+			throw new Exception("ZRLE decoder: assertion failed"
+					+ " (len <= end-ptr)");
+
+		if (bytesPixel == 1) {
+			while (len-- > 0)
+				zrleTilePixels8[ptr++] = (byte) pix;
+		} else {
+			while (len-- > 0)
+				zrleTilePixels24[ptr++] = pix;
+		}
+	}
+}
+
+void readZrlePackedRLEPixels(int tw, int th, int[] palette)
+		throws Exception {
+
+	int ptr = 0;
+	int end = ptr + tw * th;
+	while (ptr < end) {
+		int index = zrleInStream.readU8();
+		int len = 1;
+		if ((index & 128) != 0) {
+			int b;
+			do {
+				b = zrleInStream.readU8();
+				len += b;
+			} while (b == 255);
+
+			if (!(len <= end - ptr))
+				throw new Exception("ZRLE decoder: assertion failed"
+						+ " (len <= end - ptr)");
+		}
+
+		index &= 127;
+		int pix = palette[index];
+
+		if (bytesPixel == 1) {
+			while (len-- > 0)
+				zrleTilePixels8[ptr++] = (byte) pix;
+		} else {
+			while (len-- > 0)
+				zrleTilePixels24[ptr++] = pix;
+		}
+	}
+}
+
+//
+// Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
+//
+
+void handleUpdatedZrleTile(int x, int y, int w, int h) {
+	Object src, dst;
+	if (bytesPixel == 1) {
+		src = zrleTilePixels8;
+		dst = pixels8;
+	} else {
+		src = zrleTilePixels24;
+		dst = pixels24;
+	}
+	int offsetSrc = 0;
+	int offsetDst = (y * rfb.framebufferWidth + x);
+	for (int j = 0; j < h; j++) {
+		System.arraycopy(src, offsetSrc, dst, offsetDst, w);
+		offsetSrc += w;
+		offsetDst += rfb.framebufferWidth;
+	}
+	handleUpdatedPixels(x, y, w, h);
+}
+
+//
+// Handle a Zlib-encoded rectangle.
+//
+
+void handleZlibRect(int x, int y, int w, int h) throws Exception {
+
+	int nBytes = rfb.readU32();
+
+	if (zlibBuf == null || zlibBufLen < nBytes) {
+		zlibBufLen = nBytes * 2;
+		zlibBuf = new byte[zlibBufLen];
+	}
+
+	rfb.readFully(zlibBuf, 0, nBytes);
+
+	if (rfb.rec != null && rfb.recordFromBeginning) {
+		rfb.rec.writeIntBE(nBytes);
+		rfb.rec.write(zlibBuf, 0, nBytes);
+	}
+
+	if (zlibInflater == null) {
+		zlibInflater = new Inflater();
+	}
+	zlibInflater.setInput(zlibBuf, 0, nBytes);
+
+	if (bytesPixel == 1) {
+		for (int dy = y; dy < y + h; dy++) {
+			zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
+			if (rfb.rec != null && !rfb.recordFromBeginning)
+				rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
+		}
+	} else {
+		byte[] buf = new byte[w * 4];
+		int i, offset;
+		for (int dy = y; dy < y + h; dy++) {
+			zlibInflater.inflate(buf);
+			offset = dy * rfb.framebufferWidth + x;
+			for (i = 0; i < w; i++) {
+				pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16
+						| (buf[i * 4 + 1] & 0xFF) << 8
+						| (buf[i * 4] & 0xFF);
+			}
+			if (rfb.rec != null && !rfb.recordFromBeginning)
+				rfb.rec.write(buf);
+		}
+	}
+
+	handleUpdatedPixels(x, y, w, h);
+	scheduleRepaint(x, y, w, h);
+}
+
+//
+// Handle a Tight-encoded rectangle.
+//
+
+void handleTightRect(int x, int y, int w, int h) throws Exception {
+
+	int comp_ctl = rfb.readU8();
+	if (rfb.rec != null) {
+		if (rfb.recordFromBeginning || comp_ctl == (rfb.TightFill << 4)
+				|| comp_ctl == (rfb.TightJpeg << 4)) {
+			// Send data exactly as received.
+			rfb.rec.writeByte(comp_ctl);
+		} else {
+			// Tell the decoder to flush each of the four zlib streams.
+			rfb.rec.writeByte(comp_ctl | 0x0F);
+		}
+	}
+
+	// Flush zlib streams if we are told by the server to do so.
+	for (int stream_id = 0; stream_id < 4; stream_id++) {
+		if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
+			tightInflaters[stream_id] = null;
+		}
+		comp_ctl >>= 1;
+	}
+
+	// Check correctness of subencoding value.
+	if (comp_ctl > rfb.TightMaxSubencoding) {
+		throw new Exception("Incorrect tight subencoding: " + comp_ctl);
+	}
+
+	// Handle solid-color rectangles.
+	if (comp_ctl == rfb.TightFill) {
+
+		if (bytesPixel == 1) {
+			int idx = rfb.readU8();
+			memGraphics.setColor(colors[idx]);
+			if (rfb.rec != null) {
+				rfb.rec.writeByte(idx);
+			}
+		} else {
+			byte[] buf = new byte[3];
+			rfb.readFully(buf);
+			if (rfb.rec != null) {
+				rfb.rec.write(buf);
+			}
+			Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16
+					| (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
+			memGraphics.setColor(bg);
+		}
+		memGraphics.fillRect(x, y, w, h);
+		scheduleRepaint(x, y, w, h);
+		return;
+
+	}
+
+	if (comp_ctl == rfb.TightJpeg) {
+
+		statNumRectsTightJPEG++;
+
+		// Read JPEG data.
+		byte[] jpegData = new byte[rfb.readCompactLen()];
+		rfb.readFully(jpegData);
+		if (rfb.rec != null) {
+			if (!rfb.recordFromBeginning) {
+				rfb.recordCompactLen(jpegData.length);
+			}
+			rfb.rec.write(jpegData);
+		}
+
+		// Create an Image object from the JPEG data.
+		Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
+
+		// Remember the rectangle where the image should be drawn.
+		jpegRect = new Rectangle(x, y, w, h);
+
+		// Let the imageUpdate() method do the actual drawing, here just
+		// wait until the image is fully loaded and drawn.
+		synchronized (jpegRect) {
+			Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1,
+					this);
+			try {
+				// Wait no longer than three seconds.
+				jpegRect.wait(3000);
+			} catch (InterruptedException e) {
+				throw new Exception("Interrupted while decoding JPEG image");
+			}
+		}
+
+		// Done, jpegRect is not needed any more.
+		jpegRect = null;
+		return;
+
+	}
+
+	// Read filter id and parameters.
+	int numColors = 0, rowSize = w;
+	byte[] palette8 = new byte[2];
+	int[] palette24 = new int[256];
+	boolean useGradient = false;
+	if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
+		int filter_id = rfb.readU8();
+		if (rfb.rec != null) {
+			rfb.rec.writeByte(filter_id);
+		}
+		if (filter_id == rfb.TightFilterPalette) {
+			numColors = rfb.readU8() + 1;
+			if (rfb.rec != null) {
+				rfb.rec.writeByte(numColors - 1);
+			}
+			if (bytesPixel == 1) {
+				if (numColors != 2) {
+					throw new Exception("Incorrect tight palette size: "
+							+ numColors);
+				}
+				rfb.readFully(palette8);
+				if (rfb.rec != null) {
+					rfb.rec.write(palette8);
+				}
+			} else {
+				byte[] buf = new byte[numColors * 3];
+				rfb.readFully(buf);
+				if (rfb.rec != null) {
+					rfb.rec.write(buf);
+				}
+				for (int i = 0; i < numColors; i++) {
+					palette24[i] = ((buf[i * 3] & 0xFF) << 16
+							| (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3 + 2] & 0xFF));
+				}
+			}
+			if (numColors == 2)
+				rowSize = (w + 7) / 8;
+		} else if (filter_id == rfb.TightFilterGradient) {
+			useGradient = true;
+		} else if (filter_id != rfb.TightFilterCopy) {
+			throw new Exception("Incorrect tight filter id: " + filter_id);
+		}
+	}
+	if (numColors == 0 && bytesPixel == 4)
+		rowSize *= 3;
+
+	// Read, optionally uncompress and decode data.
+	int dataSize = h * rowSize;
+	if (dataSize < rfb.TightMinToCompress) {
+		// Data size is small - not compressed with zlib.
+		if (numColors != 0) {
+			// Indexed colors.
+			byte[] indexedData = new byte[dataSize];
+			rfb.readFully(indexedData);
+			if (rfb.rec != null) {
+				rfb.rec.write(indexedData);
+			}
+			if (numColors == 2) {
+				// Two colors.
+				if (bytesPixel == 1) {
+					decodeMonoData(x, y, w, h, indexedData, palette8);
+				} else {
+					decodeMonoData(x, y, w, h, indexedData, palette24);
+				}
+			} else {
+				// 3..255 colors (assuming bytesPixel == 4).
+				int i = 0;
+				for (int dy = y; dy < y + h; dy++) {
+					for (int dx = x; dx < x + w; dx++) {
+						pixels24[dy * rfb.framebufferWidth + dx] = palette24[indexedData[i++] & 0xFF];
+					}
+				}
+			}
+		} else if (useGradient) {
+			// "Gradient"-processed data
+			byte[] buf = new byte[w * h * 3];
+			rfb.readFully(buf);
+			if (rfb.rec != null) {
+				rfb.rec.write(buf);
+			}
+			decodeGradientData(x, y, w, h, buf);
+		} else {
+			// Raw truecolor data.
+			if (bytesPixel == 1) {
+				for (int dy = y; dy < y + h; dy++) {
+					rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
+					if (rfb.rec != null) {
+						rfb.rec.write(pixels8, dy * rfb.framebufferWidth
+								+ x, w);
+					}
+				}
+			} else {
+				byte[] buf = new byte[w * 3];
+				int i, offset;
+				for (int dy = y; dy < y + h; dy++) {
+					rfb.readFully(buf);
+					if (rfb.rec != null) {
+						rfb.rec.write(buf);
+					}
+					offset = dy * rfb.framebufferWidth + x;
+					for (i = 0; i < w; i++) {
+						pixels24[offset + i] = (buf[i * 3] & 0xFF) << 16
+								| (buf[i * 3 + 1] & 0xFF) << 8
+								| (buf[i * 3 + 2] & 0xFF);
+					}
+				}
+			}
+		}
+	} else {
+		// Data was compressed with zlib.
+		int zlibDataLen = rfb.readCompactLen();
+		byte[] zlibData = new byte[zlibDataLen];
+		rfb.readFully(zlibData);
+		if (rfb.rec != null && rfb.recordFromBeginning) {
+			rfb.rec.write(zlibData);
+		}
+		int stream_id = comp_ctl & 0x03;
+		if (tightInflaters[stream_id] == null) {
+			tightInflaters[stream_id] = new Inflater();
+		}
+		Inflater myInflater = tightInflaters[stream_id];
+		myInflater.setInput(zlibData);
+		byte[] buf = new byte[dataSize];
+		myInflater.inflate(buf);
+		if (rfb.rec != null && !rfb.recordFromBeginning) {
+			rfb.recordCompressedData(buf);
+		}
+
+		if (numColors != 0) {
+			// Indexed colors.
+			if (numColors == 2) {
+				// Two colors.
+				if (bytesPixel == 1) {
+					decodeMonoData(x, y, w, h, buf, palette8);
+				} else {
+					decodeMonoData(x, y, w, h, buf, palette24);
+				}
+			} else {
+				// More than two colors (assuming bytesPixel == 4).
+				int i = 0;
+				for (int dy = y; dy < y + h; dy++) {
+					for (int dx = x; dx < x + w; dx++) {
+						pixels24[dy * rfb.framebufferWidth + dx] = palette24[buf[i++] & 0xFF];
+					}
+				}
+			}
+		} else if (useGradient) {
+			// Compressed "Gradient"-filtered data (assuming bytesPixel ==
+			// 4).
+			decodeGradientData(x, y, w, h, buf);
+		} else {
+			// Compressed truecolor data.
+			if (bytesPixel == 1) {
+				int destOffset = y * rfb.framebufferWidth + x;
+				for (int dy = 0; dy < h; dy++) {
+					System.arraycopy(buf, dy * w, pixels8, destOffset, w);
+					destOffset += rfb.framebufferWidth;
+				}
+			} else {
+				int srcOffset = 0;
+				int destOffset, i;
+				for (int dy = 0; dy < h; dy++) {
+					myInflater.inflate(buf);
+					destOffset = (y + dy) * rfb.framebufferWidth + x;
+					for (i = 0; i < w; i++) {
+						pixels24[destOffset + i] = (buf[srcOffset] & 0xFF) << 16
+								| (buf[srcOffset + 1] & 0xFF) << 8
+								| (buf[srcOffset + 2] & 0xFF);
+						srcOffset += 3;
+					}
+				}
+			}
+		}
+	}
+
+	handleUpdatedPixels(x, y, w, h);
+	scheduleRepaint(x, y, w, h);
+}
+
+//
+// Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
+//
+
+void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
+
+	int dx, dy, n;
+	int i = y * rfb.framebufferWidth + x;
+	int rowBytes = (w + 7) / 8;
+	byte b;
+
+	for (dy = 0; dy < h; dy++) {
+		for (dx = 0; dx < w / 8; dx++) {
+			b = src[dy * rowBytes + dx];
+			for (n = 7; n >= 0; n--)
+				pixels8[i++] = palette[b >> n & 1];
+		}
+		for (n = 7; n >= 8 - w % 8; n--) {
+			pixels8[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
+		}
+		i += (rfb.framebufferWidth - w);
+	}
+}
+
+void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
+
+	int dx, dy, n;
+	int i = y * rfb.framebufferWidth + x;
+	int rowBytes = (w + 7) / 8;
+	byte b;
+
+	for (dy = 0; dy < h; dy++) {
+		for (dx = 0; dx < w / 8; dx++) {
+			b = src[dy * rowBytes + dx];
+			for (n = 7; n >= 0; n--)
+				pixels24[i++] = palette[b >> n & 1];
+		}
+		for (n = 7; n >= 8 - w % 8; n--) {
+			pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
+		}
+		i += (rfb.framebufferWidth - w);
+	}
+}
+
+//
+// Decode data processed with the "Gradient" filter.
+//
+
+void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
+
+	int dx, dy, c;
+	byte[] prevRow = new byte[w * 3];
+	byte[] thisRow = new byte[w * 3];
+	byte[] pix = new byte[3];
+	int[] est = new int[3];
+
+	int offset = y * rfb.framebufferWidth + x;
+
+	for (dy = 0; dy < h; dy++) {
+
+		/* First pixel in a row */
+		for (c = 0; c < 3; c++) {
+			pix[c] = (byte) (prevRow[c] + buf[dy * w * 3 + c]);
+			thisRow[c] = pix[c];
+		}
+		pixels24[offset++] = (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8
+				| (pix[2] & 0xFF);
+
+		/* Remaining pixels of a row */
+		for (dx = 1; dx < w; dx++) {
+			for (c = 0; c < 3; c++) {
+				est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) - (prevRow[(dx - 1)
+						* 3 + c] & 0xFF));
+				if (est[c] > 0xFF) {
+					est[c] = 0xFF;
+				} else if (est[c] < 0x00) {
+					est[c] = 0x00;
+				}
+				pix[c] = (byte) (est[c] + buf[(dy * w + dx) * 3 + c]);
+				thisRow[dx * 3 + c] = pix[c];
+			}
+			pixels24[offset++] = (pix[0] & 0xFF) << 16
+					| (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
+		}
+
+		System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
+		offset += (rfb.framebufferWidth - w);
+	}
+}
+
+//
+// Display newly updated area of pixels.
+//
+
+void handleUpdatedPixels(int x, int y, int w, int h) {
+
+	// Draw updated pixels of the off-screen image.
+	pixelsSource.newPixels(x, y, w, h);
+	memGraphics.setClip(x, y, w, h);
+	memGraphics.drawImage(rawPixelsImage, 0, 0, null);
+	memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
+}
+
+//
+// Tell JVM to repaint specified desktop area.
+//
+
+void scheduleRepaint(int x, int y, int w, int h) {
+	// Request repaint, deferred if necessary.
+	if (rfb.framebufferWidth == scaledWidth) {
+		repaint(viewer.deferScreenUpdates, x, y, w, h);
+	} else {
+		int sx = x * scalingFactor / 100;
+		int sy = y * scalingFactor / 100;
+		int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
+		int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
+		repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
+	}
+}
+
+//
+// Handle events.
+//
+
+public void keyPressed(KeyEvent evt) {
+	processLocalKeyEvent(evt);
+}
+
+public void keyReleased(KeyEvent evt) {
+	processLocalKeyEvent(evt);
+}
+
+public void keyTyped(KeyEvent evt) {
+	evt.consume();
+}
+
+public void mousePressed(MouseEvent evt) {
+	processLocalMouseEvent(evt, false);
+}
+
+public void mouseReleased(MouseEvent evt) {
+	processLocalMouseEvent(evt, false);
+}
+
+public void mouseMoved(MouseEvent evt) {
+	processLocalMouseEvent(evt, true);
+}
+
+public void mouseDragged(MouseEvent evt) {
+	processLocalMouseEvent(evt, true);
+}
+
+public void processLocalKeyEvent(KeyEvent evt) {
+	if (viewer.rfb != null && rfb.inNormalProtocol) {
+		if (!inputEnabled) {
+			if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R')
+					&& evt.getID() == KeyEvent.KEY_PRESSED) {
+				// Request screen update.
+				try {
+					rfb.writeFramebufferUpdateRequest(0, 0,
+							rfb.framebufferWidth, rfb.framebufferHeight,
+							false);
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		} else {
+			// Input enabled.
+			synchronized (rfb) {
+				try {
+					rfb.writeKeyEvent(evt);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+				rfb.notify();
+			}
+		}
+	}
+	// Don't ever pass keyboard events to AWT for default processing.
+	// Otherwise, pressing Tab would switch focus to ButtonPanel etc.
+	evt.consume();
+}
+
+public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
+	if (viewer.rfb != null && rfb.inNormalProtocol) {
+		if (moved) {
+			softCursorMove(evt.getX(), evt.getY());
+		}
+		if (rfb.framebufferWidth != scaledWidth) {
+			int sx = (evt.getX() * 100 + scalingFactor / 2) / scalingFactor;
+			int sy = (evt.getY() * 100 + scalingFactor / 2) / scalingFactor;
+			evt.translatePoint(sx - evt.getX(), sy - evt.getY());
+		}
+		synchronized (rfb) {
+			try {
+				rfb.writePointerEvent(evt);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+			rfb.notify();
+		}
+	}
+}
+
+//
+// Ignored events.
+//
+
+public void mouseClicked(MouseEvent evt) {
+}
+
+public void mouseEntered(MouseEvent evt) {
+}
+
+public void mouseExited(MouseEvent evt) {
+}
+
+//
+// Reset update statistics.
+//
+
+void resetStats() {
+	statStartTime = System.currentTimeMillis();
+	statNumUpdates = 0;
+	statNumTotalRects = 0;
+	statNumPixelRects = 0;
+	statNumRectsTight = 0;
+	statNumRectsTightJPEG = 0;
+	statNumRectsZRLE = 0;
+	statNumRectsHextile = 0;
+	statNumRectsRaw = 0;
+	statNumRectsCopy = 0;
+	statNumBytesEncoded = 0;
+	statNumBytesDecoded = 0;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Handle cursor shape updates (XCursor and RichCursor encodings).
+//
+
+boolean showSoftCursor = false;
+
+MemoryImageSource softCursorSource;
+Image softCursor;
+
+int cursorX = 0, cursorY = 0;
+int cursorWidth, cursorHeight;
+int origCursorWidth, origCursorHeight;
+int hotX, hotY;
+int origHotX, origHotY;
+
+//
+// Handle cursor shape update (XCursor and RichCursor encodings).
+//
+
+synchronized void handleCursorShapeUpdate(int encodingType, int xhot,
+		int yhot, int width, int height) throws IOException {
+
+	softCursorFree();
+
+	if (width * height == 0)
+		return;
+
+	// Ignore cursor shape data if requested by user.
+	if (viewer.options.ignoreCursorUpdates) {
+		int bytesPerRow = (width + 7) / 8;
+		int bytesMaskData = bytesPerRow * height;
+
+		if (encodingType == rfb.EncodingXCursor) {
+			rfb.skipBytes(6 + bytesMaskData * 2);
+		} else {
+			// rfb.EncodingRichCursor
+			rfb.skipBytes(width * height * bytesPixel + bytesMaskData);
+		}
+		return;
+	}
+
+	// Decode cursor pixel data.
+	softCursorSource = decodeCursorShape(encodingType, width, height);
+
+	// Set original (non-scaled) cursor dimensions.
+	origCursorWidth = width;
+	origCursorHeight = height;
+	origHotX = xhot;
+	origHotY = yhot;
+
+	// Create off-screen cursor image.
+	createSoftCursor();
+
+	// Show the cursor.
+	showSoftCursor = true;
+	repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
+			cursorWidth, cursorHeight);
+}
+
+//
+// decodeCursorShape(). Decode cursor pixel data and return
+// corresponding MemoryImageSource instance.
+//
+
+synchronized MemoryImageSource decodeCursorShape(int encodingType,
+		int width, int height) throws IOException {
+
+	int bytesPerRow = (width + 7) / 8;
+	int bytesMaskData = bytesPerRow * height;
+
+	int[] softCursorPixels = new int[width * height];
+
+	if (encodingType == rfb.EncodingXCursor) {
+
+		// Read foreground and background colors of the cursor.
+		byte[] rgb = new byte[6];
+		rfb.readFully(rgb);
+		int[] colors = {
+				(0xFF000000 | (rgb[3] & 0xFF) << 16 | (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
+				(0xFF000000 | (rgb[0] & 0xFF) << 16 | (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
+
+		// Read pixel and mask data.
+		byte[] pixBuf = new byte[bytesMaskData];
+		rfb.readFully(pixBuf);
+		byte[] maskBuf = new byte[bytesMaskData];
+		rfb.readFully(maskBuf);
+
+		// Decode pixel data into softCursorPixels[].
+		byte pixByte, maskByte;
+		int x, y, n, result;
+		int i = 0;
+		for (y = 0; y < height; y++) {
+			for (x = 0; x < width / 8; x++) {
+				pixByte = pixBuf[y * bytesPerRow + x];
+				maskByte = maskBuf[y * bytesPerRow + x];
+				for (n = 7; n >= 0; n--) {
+					if ((maskByte >> n & 1) != 0) {
+						result = colors[pixByte >> n & 1];
+					} else {
+						result = 0; // Transparent pixel
+					}
+					softCursorPixels[i++] = result;
+				}
+			}
+			for (n = 7; n >= 8 - width % 8; n--) {
+				if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
+					result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
+				} else {
+					result = 0; // Transparent pixel
+				}
+				softCursorPixels[i++] = result;
+			}
+		}
+
+	} else {
+		// encodingType == rfb.EncodingRichCursor
+
+		// Read pixel and mask data.
+		byte[] pixBuf = new byte[width * height * bytesPixel];
+		rfb.readFully(pixBuf);
+		byte[] maskBuf = new byte[bytesMaskData];
+		rfb.readFully(maskBuf);
+
+		// Decode pixel data into softCursorPixels[].
+		byte pixByte, maskByte;
+		int x, y, n, result;
+		int i = 0;
+		for (y = 0; y < height; y++) {
+			for (x = 0; x < width / 8; x++) {
+				maskByte = maskBuf[y * bytesPerRow + x];
+				for (n = 7; n >= 0; n--) {
+					if ((maskByte >> n & 1) != 0) {
+						if (bytesPixel == 1) {
+							result = cm8.getRGB(pixBuf[i]);
+						} else {
+							result = 0xFF000000
+									| (pixBuf[i * 4 + 2] & 0xFF) << 16
+									| (pixBuf[i * 4 + 1] & 0xFF) << 8
+									| (pixBuf[i * 4] & 0xFF);
+						}
+					} else {
+						result = 0; // Transparent pixel
+					}
+					softCursorPixels[i++] = result;
+				}
+			}
+			for (n = 7; n >= 8 - width % 8; n--) {
+				if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
+					if (bytesPixel == 1) {
+						result = cm8.getRGB(pixBuf[i]);
+					} else {
+						result = 0xFF000000
+								| (pixBuf[i * 4 + 2] & 0xFF) << 16
+								| (pixBuf[i * 4 + 1] & 0xFF) << 8
+								| (pixBuf[i * 4] & 0xFF);
+					}
+				} else {
+					result = 0; // Transparent pixel
+				}
+				softCursorPixels[i++] = result;
+			}
+		}
+
+	}
+
+	return new MemoryImageSource(width, height, softCursorPixels, 0, width);
+}
+
+//
+// createSoftCursor(). Assign softCursor new Image (scaled if necessary).
+// Uses softCursorSource as a source for new cursor image.
+//
+
+synchronized void createSoftCursor() {
+
+	if (softCursorSource == null)
+		return;
+
+	int scaleCursor = viewer.options.scaleCursor;
+	if (scaleCursor == 0 || !inputEnabled)
+		scaleCursor = 100;
+
+	// Save original cursor coordinates.
+	int x = cursorX - hotX;
+	int y = cursorY - hotY;
+	int w = cursorWidth;
+	int h = cursorHeight;
+
+	cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
+	cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
+	hotX = (origHotX * scaleCursor + 50) / 100;
+	hotY = (origHotY * scaleCursor + 50) / 100;
+	softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
+
+	if (scaleCursor != 100) {
+		softCursor = softCursor.getScaledInstance(cursorWidth,
+				cursorHeight, Image.SCALE_SMOOTH);
+	}
+
+	if (showSoftCursor) {
+		// Compute screen area to update.
+		x = Math.min(x, cursorX - hotX);
+		y = Math.min(y, cursorY - hotY);
+		w = Math.max(w, cursorWidth);
+		h = Math.max(h, cursorHeight);
+
+		repaint(viewer.deferCursorUpdates, x, y, w, h);
+	}
+}
+
+//
+// softCursorMove(). Moves soft cursor into a particular location.
+//
+
+synchronized void softCursorMove(int x, int y) {
+	int oldX = cursorX;
+	int oldY = cursorY;
+	cursorX = x;
+	cursorY = y;
+	if (showSoftCursor) {
+		repaint(viewer.deferCursorUpdates, oldX - hotX, oldY - hotY,
+				cursorWidth, cursorHeight);
+		repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
+				cursorWidth, cursorHeight);
+	}
+}
+
+//
+// softCursorFree(). Remove soft cursor, dispose resources.
+//
+
+synchronized void softCursorFree() {
+	if (showSoftCursor) {
+		showSoftCursor = false;
+		softCursor = null;
+		softCursorSource = null;
+
+		repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
+				cursorWidth, cursorHeight);
+	}
+}
+}
--- a/src/RfbProto.java	Sat Apr 16 20:43:07 2011 +0900
+++ b/src/RfbProto.java	Sun Apr 17 01:50:24 2011 +0900
@@ -178,7 +178,6 @@
 	//
 	// Constructor. Make TCP connection to RFB server.
 	//
-
 	RfbProto(String h, int p, VncViewer v) throws IOException {
 		viewer = v;
 		host = h;
@@ -208,6 +207,22 @@
 		timeWaitedIn100us = 5;
 		timedKbits = 0;
 	}
+	
+	RfbProto(String h, int p) throws IOException {
+		host = h;
+		port = p;
+
+		sock = new Socket(host, port);
+		is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
+				16384));
+		os = sock.getOutputStream();
+
+		timing = false;
+		timeWaitedIn100us = 5;
+		timedKbits = 0;
+	}
+	
+	
 
 	synchronized void close() {
 		try {
@@ -541,12 +556,13 @@
 	//
 
 	void writeClientInit() throws IOException {
+/*
 		if (viewer.options.shareDesktop) {
 			os.write(1);
-		} else {
+*/
 			os.write(0);
-		}
-		viewer.options.disableShareDesktop();
+
+//		viewer.options.disableShareDesktop();
 	}
 
 	//
--- a/src/VncCanvas.java	Sat Apr 16 20:43:07 2011 +0900
+++ b/src/VncCanvas.java	Sun Apr 17 01:50:24 2011 +0900
@@ -380,22 +380,19 @@
 		//
 
 		long count = 0;
-
+		rfb.initServSock(5550);
 		try {
-			rfb.initServSock(5550);
-//			rfb.setSoTimeout(1000);
-			Socket newCli = rfb.accept();
-			rfb.sendInitData(newCli);
-			rfb.addSock(newCli);
-		} catch (IOException e) {
+				// rfb.setSoTimeout(1000);
+				Socket newCli = rfb.accept();
+				rfb.sendInitData(newCli);
+				rfb.addSock(newCli);
+			} catch (IOException e) {
 		}
+		/*
+		 * Thread accept = new Thread(new acceptThread(rfb)); accept.start();
+		 */
+		while (true) {
 
-/*
-		Thread accept = new Thread(new acceptThread(rfb));
-		accept.start();
-*/
-		while (true) {
-			
 			if (rfb.MYVNC) {
 				if (rfb.cliSize() > 0) {
 					System.out.println("\ncount=" + count);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/VncProxyService.java	Sun Apr 17 01:50:24 2011 +0900
@@ -0,0 +1,774 @@
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.*;
+
+public class VncProxyService implements java.lang.Runnable {
+
+	public static void main(String[] argv) {
+		VncProxyService v = new VncProxyService();
+		v.mainArgs = argv;
+
+		v.init();
+//		v.start();
+		v.run();
+	}
+
+	String[] mainArgs;
+
+	// RfbProto rfb;
+	MyRfbProto rfb;
+	Thread rfbThread;
+
+	Frame vncFrame;
+	Container vncContainer;
+	ScrollPane desktopScrollPane;
+	GridBagLayout gridbag;
+	ButtonPanel buttonPanel;
+	Label connStatusLabel;
+	ProxyVncCanvas vc;
+	OptionsFrame options;
+	ClipboardFrame clipboard;
+	RecordingFrame rec;
+
+	// Control session recording.
+	Object recordingSync;
+	String sessionFileName;
+	boolean recordingActive;
+	boolean recordingStatusChanged;
+	String cursorUpdatesDef;
+	String eightBitColorsDef;
+
+	// Variables read from parameter values.
+	String socketFactory;
+	String host;
+	int port;
+	String passwordParam;
+	boolean showControls;
+	boolean offerRelogin;
+	boolean showOfflineDesktop;
+	int deferScreenUpdates;
+	int deferCursorUpdates;
+	int deferUpdateRequests;
+	int debugStatsExcludeUpdates;
+	int debugStatsMeasureUpdates;
+
+	//
+	// init()
+	//
+
+	public void init() {
+
+		readParameters();
+
+		recordingSync = new Object();
+
+		sessionFileName = null;
+		recordingActive = false;
+		recordingStatusChanged = false;
+		cursorUpdatesDef = null;
+		eightBitColorsDef = null;
+
+		rfbThread = new Thread(this);
+		rfbThread.start();
+	}
+
+	//
+	// run() - executed by the rfbThread to deal with the RFB socket.
+	//
+
+	public void run() {
+
+		try {
+			connectAndAuthenticate();
+			doProtocolInitialisation();
+
+			vc = new ProxyVncCanvas(this, 0, 0);
+			
+
+			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 (rfb != null && !rfb.closed())
+					rfb.close();
+				if (showControls && buttonPanel != null) {
+					buttonPanel.disableButtonsOnDisconnect();
+				}
+			} 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);
+			}
+		}
+
+	}
+
+	//
+	// Process RFB socket messages.
+	// If the rfbThread is being stopped, ignore any exceptions,
+	// otherwise rethrow the exception so it can be handled.
+	//
+
+	void processNormalProtocol() throws Exception {
+		try {
+			vc.processNormalProtocol();// main loop
+		} catch (Exception e) {
+			if (rfbThread == null) {
+				System.out.println("Ignoring RFB socket exceptions"
+						+ " because applet is stopping");
+			} else {
+				throw e;
+			}
+		}
+	}
+
+	//
+	// Connect to the RFB server and authenticate the user.
+	//
+
+	void connectAndAuthenticate() throws Exception {
+		showConnectionStatus("Initializing...");
+
+		showConnectionStatus("Connecting to " + host + ", port " + port + "...");
+
+		// rfb = new RfbProto(host, port, this);
+		rfb = new MyRfbProto(host, port);
+		showConnectionStatus("Connected to server");
+
+		rfb.readVersionMsg();
+		showConnectionStatus("RFB server supports protocol version "
+				+ rfb.serverMajor + "." + rfb.serverMinor);
+
+		rfb.writeVersionMsg();
+		showConnectionStatus("Using RFB protocol version " + rfb.clientMajor
+				+ "." + rfb.clientMinor);
+
+		int secType = rfb.negotiateSecurity();
+		int authType;
+		if (secType == RfbProto.SecTypeTight) {
+			showConnectionStatus("Enabling TightVNC protocol extensions");
+			rfb.setupTunneling();
+			authType = rfb.negotiateAuthenticationTight();
+		} else {
+			authType = secType;
+		}
+
+		switch (authType) {
+		case RfbProto.AuthNone:
+			showConnectionStatus("No authentication needed");
+			rfb.authenticateNone();
+			break;
+		case RfbProto.AuthVNC:
+			showConnectionStatus("Performing standard VNC authentication");
+			if (passwordParam != null) {
+				rfb.authenticateVNC(passwordParam);
+			} else {
+				String pw = askPassword();
+				rfb.authenticateVNC(pw);
+			}
+			break;
+		default:
+			throw new Exception("Unknown authentication scheme " + authType);
+		}
+	}
+
+	//
+	// Show a message describing the connection status.
+	// To hide the connection status label, use (msg == null).
+	//
+
+	void showConnectionStatus(String msg) {
+		System.out.println(msg);
+	}
+
+	//
+	// Show an authentication panel.
+	//
+
+	String askPassword() throws Exception {
+		showConnectionStatus(null);
+		/*
+		 * AuthPanel authPanel = new AuthPanel(this);
+		 * 
+		 * GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth =
+		 * GridBagConstraints.REMAINDER; gbc.anchor =
+		 * GridBagConstraints.NORTHWEST; gbc.weightx = 1.0; gbc.weighty = 1.0;
+		 * gbc.ipadx = 100; gbc.ipady = 50; gridbag.setConstraints(authPanel,
+		 * gbc); vncContainer.add(authPanel);
+
+
+		authPanel.moveFocusToDefaultField();
+		vncContainer.remove(authPanel);
+		 */
+		String pw = mainArgs[2];
+		return pw;
+	}
+
+	//
+	// Do the rest of the protocol initialisation.
+	//
+
+	void doProtocolInitialisation() throws IOException {
+		rfb.writeClientInit();
+		rfb.readServerInit();
+
+		/*
+		 * if (rfb.MYVNC) { rfb.initServSock(5550);
+		 * 
+		 * try { Socket newCli = rfb.accept(); rfb.sendInitData(newCli);
+		 * rfb.addSock(newCli); } catch (IOException e) { }
+		 * 
+		 * }
+		 */
+
+		System.out.println("Desktop name is " + rfb.desktopName);
+		System.out.println("Desktop size is " + rfb.framebufferWidth + " x "
+				+ rfb.framebufferHeight);
+
+		setEncodings();
+
+		showConnectionStatus(null);
+	}
+
+	//
+	// Send current encoding list to the RFB server.
+	//
+
+	int[] encodingsSaved;
+	int nEncodingsSaved;
+
+	void setEncodings() {
+		setEncodings(false);
+	}
+
+	void autoSelectEncodings() {
+		setEncodings(true);
+	}
+
+	void setEncodings(boolean autoSelectOnly) {
+		if (options == null || rfb == null || !rfb.inNormalProtocol)
+			return;
+
+		int preferredEncoding = options.preferredEncoding;
+		if (preferredEncoding == -1) {
+			long kbitsPerSecond = rfb.kbitsPerSecond();
+			if (nEncodingsSaved < 1) {
+				// Choose Tight or ZRLE encoding for the very first update.
+				System.out.println("Using Tight/ZRLE encodings");
+				preferredEncoding = RfbProto.EncodingTight;
+			} else if (kbitsPerSecond > 2000
+					&& encodingsSaved[0] != RfbProto.EncodingHextile) {
+				// Switch to Hextile if the connection speed is above 2Mbps.
+				System.out.println("Throughput " + kbitsPerSecond
+						+ " kbit/s - changing to Hextile encoding");
+				preferredEncoding = RfbProto.EncodingHextile;
+			} else if (kbitsPerSecond < 1000
+					&& encodingsSaved[0] != RfbProto.EncodingTight) {
+				// Switch to Tight/ZRLE if the connection speed is below 1Mbps.
+				System.out.println("Throughput " + kbitsPerSecond
+						+ " kbit/s - changing to Tight/ZRLE encodings");
+				preferredEncoding = RfbProto.EncodingTight;
+			} else {
+				// Don't change the encoder.
+				if (autoSelectOnly)
+					return;
+				preferredEncoding = encodingsSaved[0];
+			}
+		} else {
+			// Auto encoder selection is not enabled.
+			if (autoSelectOnly)
+				return;
+		}
+
+		int[] encodings = new int[20];
+		int nEncodings = 0;
+
+		encodings[nEncodings++] = preferredEncoding;
+		if (options.useCopyRect) {
+			encodings[nEncodings++] = RfbProto.EncodingCopyRect;
+		}
+
+		if (preferredEncoding != RfbProto.EncodingTight) {
+			encodings[nEncodings++] = RfbProto.EncodingTight;
+		}
+		if (preferredEncoding != RfbProto.EncodingZRLE) {
+			encodings[nEncodings++] = RfbProto.EncodingZRLE;
+		}
+		if (preferredEncoding != RfbProto.EncodingHextile) {
+			encodings[nEncodings++] = RfbProto.EncodingHextile;
+		}
+		if (preferredEncoding != RfbProto.EncodingZlib) {
+			encodings[nEncodings++] = RfbProto.EncodingZlib;
+		}
+		if (preferredEncoding != RfbProto.EncodingCoRRE) {
+			encodings[nEncodings++] = RfbProto.EncodingCoRRE;
+		}
+		if (preferredEncoding != RfbProto.EncodingRRE) {
+			encodings[nEncodings++] = RfbProto.EncodingRRE;
+		}
+
+		if (options.compressLevel >= 0 && options.compressLevel <= 9) {
+			encodings[nEncodings++] = RfbProto.EncodingCompressLevel0
+					+ options.compressLevel;
+		}
+		if (options.jpegQuality >= 0 && options.jpegQuality <= 9) {
+			encodings[nEncodings++] = RfbProto.EncodingQualityLevel0
+					+ options.jpegQuality;
+		}
+
+		if (options.requestCursorUpdates) {
+			encodings[nEncodings++] = RfbProto.EncodingXCursor;
+			encodings[nEncodings++] = RfbProto.EncodingRichCursor;
+			if (!options.ignoreCursorUpdates)
+				encodings[nEncodings++] = RfbProto.EncodingPointerPos;
+		}
+
+		encodings[nEncodings++] = RfbProto.EncodingLastRect;
+		encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
+
+		boolean encodingsWereChanged = false;
+		if (nEncodings != nEncodingsSaved) {
+			encodingsWereChanged = true;
+		} else {
+			for (int i = 0; i < nEncodings; i++) {
+				if (encodings[i] != encodingsSaved[i]) {
+					encodingsWereChanged = true;
+					break;
+				}
+			}
+		}
+
+		if (encodingsWereChanged) {
+			try {
+				rfb.writeSetEncodings(encodings, nEncodings);
+				if (vc != null) {
+					vc.softCursorFree();
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+			encodingsSaved = encodings;
+			nEncodingsSaved = nEncodings;
+		}
+	}
+
+	//
+	// setCutText() - send the given cut text to the RFB server.
+	//
+
+	void setCutText(String text) {
+		try {
+			if (rfb != null && rfb.inNormalProtocol) {
+				rfb.writeClientCutText(text);
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	//
+	// Order change in session recording status. To stop recording, pass
+	// null in place of the fname argument.
+	//
+
+	void setRecordingStatus(String fname) {
+		synchronized (recordingSync) {
+			sessionFileName = fname;
+			recordingStatusChanged = true;
+		}
+	}
+
+	//
+	// Start or stop session recording. Returns true if this method call
+	// causes recording of a new session.
+	//
+
+	boolean checkRecordingStatus() throws IOException {
+		synchronized (recordingSync) {
+			if (recordingStatusChanged) {
+				recordingStatusChanged = false;
+				if (sessionFileName != null) {
+					startRecording();
+					return true;
+				} else {
+					stopRecording();
+				}
+			}
+		}
+		return false;
+	}
+
+	//
+	// Start session recording.
+	//
+
+	protected void startRecording() throws IOException {
+		synchronized (recordingSync) {
+			if (!recordingActive) {
+				// Save settings to restore them after recording the session.
+				cursorUpdatesDef = options.choices[options.cursorUpdatesIndex]
+						.getSelectedItem();
+				eightBitColorsDef = options.choices[options.eightBitColorsIndex]
+						.getSelectedItem();
+				// Set options to values suitable for recording.
+				options.choices[options.cursorUpdatesIndex].select("Disable");
+				options.choices[options.cursorUpdatesIndex].setEnabled(false);
+				options.setEncodings();
+				options.choices[options.eightBitColorsIndex].select("No");
+				options.choices[options.eightBitColorsIndex].setEnabled(false);
+				options.setColorFormat();
+			} else {
+				rfb.closeSession();
+			}
+
+			System.out.println("Recording the session in " + sessionFileName);
+			rfb.startSession(sessionFileName);
+			recordingActive = true;
+		}
+	}
+
+	//
+	// Stop session recording.
+	//
+
+	protected void stopRecording() throws IOException {
+		synchronized (recordingSync) {
+			if (recordingActive) {
+				// Restore options.
+				options.choices[options.cursorUpdatesIndex]
+						.select(cursorUpdatesDef);
+				options.choices[options.cursorUpdatesIndex].setEnabled(true);
+				options.setEncodings();
+				options.choices[options.eightBitColorsIndex]
+						.select(eightBitColorsDef);
+				options.choices[options.eightBitColorsIndex].setEnabled(true);
+				options.setColorFormat();
+
+				rfb.closeSession();
+				System.out.println("Session recording stopped.");
+			}
+			sessionFileName = null;
+			recordingActive = false;
+		}
+	}
+
+	//
+	// readParameters() - read parameters from the html source or from the
+	// command line. On the command line, the arguments are just a sequence of
+	// param_name/param_value pairs where the names and values correspond to
+	// those expected in the html applet tag source.
+	//
+
+	void readParameters() {
+
+		if (mainArgs.length > 0)
+			host = mainArgs[0];
+		else
+			host = "hades.cr.ie.u-ryukyu.ac.jp";
+
+		if (mainArgs.length > 1)
+			port = Integer.parseInt(mainArgs[1]);
+		else
+			port = 5900;
+
+		// Read "ENCPASSWORD" or "PASSWORD" parameter if specified.
+//		readPasswordParameters();
+
+		String str;
+
+		// "Show Controls" set to "No" disables button panel.
+		showControls = true;
+		str = readParameter("Show Controls", false);
+		if (str != null && str.equalsIgnoreCase("No"))
+			showControls = false;
+
+		// "Offer Relogin" set to "No" disables "Login again" and "Close
+		// window" buttons under error messages in applet mode.
+		offerRelogin = true;
+		str = readParameter("Offer Relogin", false);
+		if (str != null && str.equalsIgnoreCase("No"))
+			offerRelogin = false;
+
+		// Do we continue showing desktop on remote disconnect?
+		showOfflineDesktop = false;
+		str = readParameter("Show Offline Desktop", false);
+		if (str != null && str.equalsIgnoreCase("Yes"))
+			showOfflineDesktop = true;
+
+		// Fine tuning options.
+		deferScreenUpdates = readIntParameter("Defer screen updates", 20);
+		deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
+		deferUpdateRequests = readIntParameter("Defer update requests", 0);
+
+		// Debugging options.
+		debugStatsExcludeUpdates = readIntParameter("DEBUG_XU", 0);
+		debugStatsMeasureUpdates = readIntParameter("DEBUG_CU", 0);
+
+		// SocketFactory.
+		socketFactory = readParameter("SocketFactory", false);
+	}
+
+	//
+	// Read password parameters. If an "ENCPASSWORD" parameter is set,
+	// then decrypt the password into the passwordParam string. Otherwise,
+	// try to read the "PASSWORD" parameter directly to passwordParam.
+	//
+
+	private void readPasswordParameters() {
+		// String encPasswordParam = readParameter("ENCPASSWORD", false);
+		String encPasswordParam = mainArgs[2];
+
+		if (encPasswordParam == null) {
+			// passwordParam = readParameter("PASSWORD", false);
+
+		} else {
+			// ENCPASSWORD is hexascii-encoded. Decode.
+			byte[] pw = { 0, 0, 0, 0, 0, 0, 0, 0 };
+			int len = encPasswordParam.length() / 2;
+			if (len > 8)
+				len = 8;
+			for (int i = 0; i < len; i++) {
+				String hex = encPasswordParam.substring(i * 2, i * 2 + 2);
+				Integer x = new Integer(Integer.parseInt(hex, 16));
+				pw[i] = x.byteValue();
+			}
+			// Decrypt the password.
+			byte[] key = { 23, 82, 107, 6, 35, 78, 88, 7 };
+			DesCipher des = new DesCipher(key);
+			des.decrypt(pw, 0, pw, 0);
+			passwordParam = new String(pw);
+
+		}
+	}
+
+	public String readParameter(String name, boolean required) {
+		for (int i = 0; i < mainArgs.length; i += 2) {
+			if (mainArgs[i].equalsIgnoreCase(name)) {
+				try {
+					return mainArgs[i + 1];
+				} catch (Exception e) {
+					if (required) {
+						fatalError(name + " parameter not specified");
+					}
+					return null;
+				}
+			}
+		}
+		if (required) {
+			fatalError(name + " parameter not specified");
+		}
+		return null;
+	}
+
+	int readIntParameter(String name, int defaultValue) {
+		String str = readParameter(name, false);
+		int result = defaultValue;
+		if (str != null) {
+			try {
+				result = Integer.parseInt(str);
+			} catch (NumberFormatException e) {
+			}
+		}
+		return result;
+	}
+
+	//
+	// disconnect() - close connection to server.
+	//
+
+	synchronized public void disconnect() {
+		System.out.println("Disconnecting");
+
+		if (vc != null) {
+			double sec = (System.currentTimeMillis() - vc.statStartTime) / 1000.0;
+			double rate = Math.round(vc.statNumUpdates / sec * 100) / 100.0;
+			int nRealRects = vc.statNumPixelRects;
+			int nPseudoRects = vc.statNumTotalRects - vc.statNumPixelRects;
+			System.out.println("Updates received: " + vc.statNumUpdates + " ("
+					+ nRealRects + " rectangles + " + nPseudoRects
+					+ " pseudo), " + rate + " updates/sec");
+			int numRectsOther = nRealRects - vc.statNumRectsTight
+					- vc.statNumRectsZRLE - vc.statNumRectsHextile
+					- vc.statNumRectsRaw - vc.statNumRectsCopy;
+			System.out.println("Rectangles:" + " Tight=" + vc.statNumRectsTight
+					+ "(JPEG=" + vc.statNumRectsTightJPEG + ") ZRLE="
+					+ vc.statNumRectsZRLE + " Hextile="
+					+ vc.statNumRectsHextile + " Raw=" + vc.statNumRectsRaw
+					+ " CopyRect=" + vc.statNumRectsCopy + " other="
+					+ numRectsOther);
+
+			int raw = vc.statNumBytesDecoded;
+			int compressed = vc.statNumBytesEncoded;
+			if (compressed > 0) {
+				double ratio = Math.round((double) raw / compressed * 1000) / 1000.0;
+				System.out.println("Pixel data: " + vc.statNumBytesDecoded
+						+ " bytes, " + vc.statNumBytesEncoded
+						+ " compressed, ratio " + ratio);
+			}
+		}
+
+		if (rfb != null && !rfb.closed())
+			rfb.close();
+		options.dispose();
+		clipboard.dispose();
+		if (rec != null)
+			rec.dispose();
+
+		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);
+		System.exit(1);
+	}
+
+	synchronized public void fatalError(String str, Exception e) {
+
+		if (rfb != null && rfb.closed()) {
+			// Not necessary to show error message if the error was caused
+			// by I/O problems after the rfb.close() method call.
+			System.out.println("RFB thread finished");
+			return;
+		}
+
+		System.out.println(str);
+		e.printStackTrace();
+
+		if (rfb != null)
+			rfb.close();
+
+		System.exit(1);
+
+	}
+
+	//
+	// Show message text and optionally "Relogin" and "Close" buttons.
+	//
+
+	void showMessage(String msg) {
+		vncContainer.removeAll();
+
+		Label errLabel = new Label(msg, Label.CENTER);
+		errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12));
+
+		if (offerRelogin) {
+			/*
+			 * Panel gridPanel = new Panel(new GridLayout(0, 1)); Panel
+			 * outerPanel = new Panel(new FlowLayout(FlowLayout.LEFT));
+			 * outerPanel.add(gridPanel); vncContainer.setLayout(new
+			 * FlowLayout(FlowLayout.LEFT, 30, 16));
+			 * vncContainer.add(outerPanel); Panel textPanel = new Panel(new
+			 * FlowLayout(FlowLayout.CENTER)); textPanel.add(errLabel);
+			 * gridPanel.add(textPanel); gridPanel.add(new ReloginPanel(this));
+			 */
+		} else {
+			/*
+			 * vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
+			 * vncContainer.add(errLabel);
+			 */
+		}
+
+	}
+
+	//
+	// Stop the applet.
+	// Main applet thread will terminate on first exception
+	// after seeing that rfbThread has been set to null.
+	//
+
+	public void stop() {
+		System.out.println("Stopping applet");
+		rfbThread = null;
+	}
+
+	//
+	// This method is called before the applet is destroyed.
+	//
+
+	public void destroy() {
+		System.out.println("Destroying applet");
+
+		vncContainer.removeAll();
+		options.dispose();
+		clipboard.dispose();
+		if (rec != null)
+			rec.dispose();
+		if (rfb != null && !rfb.closed())
+			rfb.close();
+	}
+
+	//
+	// Start/stop receiving mouse events.
+	//
+
+	public void enableInput(boolean enable) {
+		vc.enableInput(enable);
+	}
+
+	//
+	// Close application properly on window close event.
+	//
+
+	public void windowClosing(WindowEvent evt) {
+		System.out.println("Closing window");
+		if (rfb != null)
+			disconnect();
+
+		vncContainer.hide();
+
+	}
+
+	//
+	// Ignore window events we're not interested in.
+	//
+
+	public void windowActivated(WindowEvent evt) {
+	}
+
+	public void windowDeactivated(WindowEvent evt) {
+	}
+
+	public void windowOpened(WindowEvent evt) {
+	}
+
+	public void windowClosed(WindowEvent evt) {
+	}
+
+	public void windowIconified(WindowEvent evt) {
+	}
+
+	public void windowDeiconified(WindowEvent evt) {
+	}
+}