changeset 52:472a9bcacb21 draft default tip

TightVNC 2.7.1.0
author you@cr.ie.u-ryukyu.ac.jp
date Wed, 07 Aug 2013 19:01:17 +0900
parents 4689cc86d6cb
children
files README.txt build.gradle src/libs/jsch-0.1.50.jar src/main/java/com/glavsoft/core/SettingsChangedEvent.java src/main/java/com/glavsoft/drawing/ColorDecoder.java src/main/java/com/glavsoft/drawing/Renderer.java src/main/java/com/glavsoft/drawing/SoftCursor.java src/main/java/com/glavsoft/exceptions/AuthenticationFailedException.java src/main/java/com/glavsoft/exceptions/ClosedConnectionException.java src/main/java/com/glavsoft/exceptions/CommonException.java src/main/java/com/glavsoft/exceptions/CryptoException.java src/main/java/com/glavsoft/exceptions/FatalException.java src/main/java/com/glavsoft/exceptions/ProtocolException.java src/main/java/com/glavsoft/exceptions/TransportException.java src/main/java/com/glavsoft/exceptions/UnsupportedProtocolVersionException.java src/main/java/com/glavsoft/exceptions/UnsupportedSecurityTypeException.java src/main/java/com/glavsoft/rfb/CapabilityContainer.java src/main/java/com/glavsoft/rfb/ClipboardController.java src/main/java/com/glavsoft/rfb/IChangeSettingsListener.java src/main/java/com/glavsoft/rfb/IPasswordRetriever.java src/main/java/com/glavsoft/rfb/IRepaintController.java src/main/java/com/glavsoft/rfb/IRfbSessionListener.java src/main/java/com/glavsoft/rfb/RfbCapabilityInfo.java src/main/java/com/glavsoft/rfb/client/ClientCutTextMessage.java src/main/java/com/glavsoft/rfb/client/ClientToServerMessage.java src/main/java/com/glavsoft/rfb/client/FramebufferUpdateRequestMessage.java src/main/java/com/glavsoft/rfb/client/KeyEventMessage.java src/main/java/com/glavsoft/rfb/client/PointerEventMessage.java src/main/java/com/glavsoft/rfb/client/SetEncodingsMessage.java src/main/java/com/glavsoft/rfb/client/SetPixelFormatMessage.java src/main/java/com/glavsoft/rfb/encoding/EncodingType.java src/main/java/com/glavsoft/rfb/encoding/PixelFormat.java src/main/java/com/glavsoft/rfb/encoding/ServerInitMessage.java src/main/java/com/glavsoft/rfb/encoding/decoder/ByteBuffer.java src/main/java/com/glavsoft/rfb/encoding/decoder/CopyRectDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/Decoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/DecodersContainer.java src/main/java/com/glavsoft/rfb/encoding/decoder/FramebufferUpdateRectangle.java src/main/java/com/glavsoft/rfb/encoding/decoder/HextileDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/RREDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/RawDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/RichCursorDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/TightDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java src/main/java/com/glavsoft/rfb/encoding/decoder/ZlibDecoder.java src/main/java/com/glavsoft/rfb/protocol/LocalPointer.java src/main/java/com/glavsoft/rfb/protocol/MessageQueue.java src/main/java/com/glavsoft/rfb/protocol/Protocol.java src/main/java/com/glavsoft/rfb/protocol/ProtocolContext.java src/main/java/com/glavsoft/rfb/protocol/ProtocolSettings.java src/main/java/com/glavsoft/rfb/protocol/ReceiverTask.java src/main/java/com/glavsoft/rfb/protocol/SenderTask.java src/main/java/com/glavsoft/rfb/protocol/auth/AuthHandler.java src/main/java/com/glavsoft/rfb/protocol/auth/NoneAuthentication.java src/main/java/com/glavsoft/rfb/protocol/auth/SecurityType.java src/main/java/com/glavsoft/rfb/protocol/auth/TightAuthentication.java src/main/java/com/glavsoft/rfb/protocol/auth/VncAuthentication.java src/main/java/com/glavsoft/rfb/protocol/state/AuthenticationState.java src/main/java/com/glavsoft/rfb/protocol/state/HandshakeState.java src/main/java/com/glavsoft/rfb/protocol/state/InitState.java src/main/java/com/glavsoft/rfb/protocol/state/InitTightState.java src/main/java/com/glavsoft/rfb/protocol/state/ProtocolState.java src/main/java/com/glavsoft/rfb/protocol/state/SecurityType33State.java src/main/java/com/glavsoft/rfb/protocol/state/SecurityType37State.java src/main/java/com/glavsoft/rfb/protocol/state/SecurityTypeState.java src/main/java/com/glavsoft/transport/Reader.java src/main/java/com/glavsoft/transport/Writer.java src/main/java/com/glavsoft/utils/Keymap.java src/main/java/com/glavsoft/utils/Strings.java src/viewer_swing/java/com/glavsoft/viewer/AbstractConnectionWorkerFactory.java src/viewer_swing/java/com/glavsoft/viewer/CancelConnectionException.java src/viewer_swing/java/com/glavsoft/viewer/ConnectionErrorException.java src/viewer_swing/java/com/glavsoft/viewer/ConnectionPresenter.java src/viewer_swing/java/com/glavsoft/viewer/ConnectionWorker.java src/viewer_swing/java/com/glavsoft/viewer/NetworkConnectionWorker.java src/viewer_swing/java/com/glavsoft/viewer/RfbConnectionWorker.java src/viewer_swing/java/com/glavsoft/viewer/UiSettings.java src/viewer_swing/java/com/glavsoft/viewer/UiSettingsData.java src/viewer_swing/java/com/glavsoft/viewer/Viewer.java src/viewer_swing/java/com/glavsoft/viewer/cli/Parser.java src/viewer_swing/java/com/glavsoft/viewer/mvp/Model.java src/viewer_swing/java/com/glavsoft/viewer/mvp/Presenter.java src/viewer_swing/java/com/glavsoft/viewer/mvp/View.java src/viewer_swing/java/com/glavsoft/viewer/swing/ClipboardControllerImpl.java src/viewer_swing/java/com/glavsoft/viewer/swing/ConnectionParams.java src/viewer_swing/java/com/glavsoft/viewer/swing/KeyEventListener.java src/viewer_swing/java/com/glavsoft/viewer/swing/KeyboardConvertor.java src/viewer_swing/java/com/glavsoft/viewer/swing/LocalMouseCursorShape.java src/viewer_swing/java/com/glavsoft/viewer/swing/ModifierButtonEventListener.java src/viewer_swing/java/com/glavsoft/viewer/swing/MouseEventListener.java src/viewer_swing/java/com/glavsoft/viewer/swing/ParametersHandler.java src/viewer_swing/java/com/glavsoft/viewer/swing/RendererImpl.java src/viewer_swing/java/com/glavsoft/viewer/swing/SoftCursorImpl.java src/viewer_swing/java/com/glavsoft/viewer/swing/Surface.java src/viewer_swing/java/com/glavsoft/viewer/swing/SwingConnectionWorkerFactory.java src/viewer_swing/java/com/glavsoft/viewer/swing/SwingNetworkConnectionWorker.java src/viewer_swing/java/com/glavsoft/viewer/swing/SwingRfbConnectionWorker.java src/viewer_swing/java/com/glavsoft/viewer/swing/SwingViewerWindow.java src/viewer_swing/java/com/glavsoft/viewer/swing/SwingViewerWindowFactory.java src/viewer_swing/java/com/glavsoft/viewer/swing/Utils.java src/viewer_swing/java/com/glavsoft/viewer/swing/WrongParameterException.java src/viewer_swing/java/com/glavsoft/viewer/swing/gui/AutoCompletionComboEditorDocument.java src/viewer_swing/java/com/glavsoft/viewer/swing/gui/ConnectionView.java src/viewer_swing/java/com/glavsoft/viewer/swing/gui/ConnectionsHistory.java src/viewer_swing/java/com/glavsoft/viewer/swing/gui/HostnameComboboxRenderer.java src/viewer_swing/java/com/glavsoft/viewer/swing/gui/OptionsDialog.java src/viewer_swing/java/com/glavsoft/viewer/swing/gui/PasswordDialog.java src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshConnectionManager.java src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshKnownHostsManager.java src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SwingSshUserInfo.java src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fit.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fullscreen.png src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-dot.png src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-nocursor.png src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-smalldot.png src/web/viewer-applet-example.html
diffstat 116 files changed, 5607 insertions(+), 1302 deletions(-) [+]
line wrap: on
line diff
--- a/README.txt	Tue Jul 03 13:20:49 2012 +0900
+++ b/README.txt	Wed Aug 07 19:01:17 2013 +0900
@@ -1,6 +1,6 @@
 
-TightVNC Java Viewer version 2.5.0
-Copyright (C) 2011 GlavSoft LLC. All rights reserved.
+TightVNC Java Viewer version 2.7.2
+Copyright (C) 2011, 2012 GlavSoft LLC. All rights reserved.
 ======================================================================
 
 This software is distributed under the GNU General Public Licence
--- a/build.gradle	Tue Jul 03 13:20:49 2012 +0900
+++ b/build.gradle	Wed Aug 07 19:01:17 2013 +0900
@@ -3,15 +3,16 @@
 
 sourceCompatibility = 1.6
 targetCompatibility = 1.6
-version = '2.5.0'
+version = '2.7.2'
 
-baseName = 'tightvnc-jviewer'
+project.ext.baseName = 'tightvnc-jviewer'
+def buildNo = processBuildNo(version)
 
 defaultTasks 'clean', 'dist'
 
 configurations {
-	viewerSwingCompile { extendsFrom compile }
-	viewerSwingRuntime { extendsFrom viewerSwingCompile, runtime }
+    viewerSwingCompile { extendsFrom compile }
+    viewerSwingRuntime { extendsFrom viewerSwingCompile, runtime }
 }
 
 sourceSets {
@@ -29,46 +30,102 @@
 	}
 }
 
+repositories {
+	flatDir {
+		dirs 'src/libs/'
+	}
+}
+
+dependencies {
+    viewerSwingCompile group: 'com.jcraft', name: 'jsch', version: '0.1.+', ext: 'jar'
+    viewerSwingRuntime configurations.viewerSwingCompile
+    testCompile group: 'junit', name: 'junit', version: '4.+'
+}
+
+def manifestAttributes = ['Main-Class': 'com.glavsoft.viewer.Viewer',
+        'Implementation-Version': "${project.version} (${buildNo})",
+        'Implementation-Title': 'TightVNC Viewer',
+        'Implementation-Vendor': 'GlavSoft LLC.']
+
 jar {
-	baseName = project.baseName
-	version = null
+    baseName = project.baseName
+    version = null
     manifest {
-        attributes 'Main-Class': 'com.glavsoft.viewer.Viewer'
-        attributes 'Implementation-Version': "${project.version}"
+        attributes manifestAttributes
+    }
+    def runtimeDeps = configurations.viewerSwingRuntime.collect {
+        it.isDirectory() ? it : zipTree(it)
+    }
+    from(runtimeDeps) {
+        exclude 'META-INF/**'
     }
 }
 
-repositories {
-    mavenCentral()
+task noSshJar (type: Jar, dependsOn: classes) {
+    baseName = 'nossh/' + project.baseName
+    version = null
+    manifest {
+        attributes manifestAttributes
+    }
+    from sourceSets.main.output
 }
 
-dependencies {
-    testCompile group: 'junit', name: 'junit', version: '4.+'
-    archives fileTree(dir: 'src/web', include: '*.html')
+artifacts {
+    archives file('src/web/viewer-applet-example.html')
+    archives noSshJar
 }
 
 uploadArchives {
     repositories {
-		add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
-			name = 'repo'
-			addArtifactPattern "$projectDir/dist/${project.baseName}-${project.version}/${project.baseName}.[ext]"
-			descriptor = 'optional'
-			checkmodified = true
-		}
-	}
+        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+            addArtifactPattern("$projectDir/dist/${project.baseName}-${project.version}/[artifact].[ext]")
+        }
+    }
 	uploadDescriptor = false
 }
 
-task dist(dependsOn: uploadArchives) << {
-	otherFilesUpload("$projectDir/dist/${project.baseName}-${project.version}")
-}
-def otherFilesUpload(repoDir) {
-	copy {
-		from 'src/web'
-		include '*-applet-*.html'
-		expand (['archive_name' : project.baseName])
-		into repoDir
-	}
+task dist(dependsOn: uploadArchives)
+
+def processBuildNo(currentVersion) {
+    final String VERSION = 'version'
+    final String BUILD = 'build'
+
+    def lastVersion = currentVersion
+    def lastBuild = 0
+    def buildNoFile = new File('.build_no')
+    if ( ! buildNoFile.exists()) {
+        buildNoFile.createNewFile()
+        buildNoFile << "${VERSION}=${lastVersion}\n${BUILD}=${lastBuild}"
+    }
+    def versions = [:]
+    buildNoFile.eachLine {
+        def splitted = it.split('=')
+        if (splitted.size() == 2) {
+            def (key, value) = splitted
+            switch(key.trim()) {
+                case VERSION:
+                    lastVersion = value.trim()
+                    break
+                case BUILD:
+                    try {
+                        lastBuild = value != null ? value.trim() as Integer : 0
+                    } catch (NumberFormatException) {}
+                    versions[lastVersion] = lastBuild
+                    break
+            }
+        }
+    }
+    lastVersion = versions[currentVersion]
+    if (null == lastVersion) {
+        versions[currentVersion] = 0
+    }
+    ++versions[currentVersion]
+    def outString = ''
+    versions.each { v, b ->
+        outString += "${VERSION}=${v}\n${BUILD}=${b}\n\n"
+    }
+    buildNoFile.write(outString)
+    versions[currentVersion]
 }
 
 
@@ -385,3 +442,50 @@
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Binary file src/libs/jsch-0.1.50.jar has changed
--- a/src/main/java/com/glavsoft/core/SettingsChangedEvent.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/core/SettingsChangedEvent.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/drawing/ColorDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/drawing/ColorDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -29,14 +29,15 @@
 import com.glavsoft.transport.Reader;
 
 public class ColorDecoder {
-	protected byte redShift;
+    protected byte redShift;
 	protected byte greenShift;
 	protected byte blueShift;
 	public short redMax;
 	public short greenMax;
 	public short blueMax;
-	private final int bytesPerPixel;
-	private final int bytesPerPixelSignificant;
+	public final int bytesPerPixel;
+	public final int bytesPerCPixel;
+    public final int bytesPerPixelTight;
 	private final byte[] buff;
 
 	private int startShift;
@@ -44,7 +45,7 @@
 	private int addShiftItem;
 	private final boolean isTightSpecific;
 
-	public ColorDecoder(PixelFormat pf) {
+    public ColorDecoder(PixelFormat pf) {
 		redShift = pf.redShift;
 		greenShift = pf.greenShift;
 		blueShift = pf.blueShift;
@@ -52,7 +53,14 @@
 		greenMax = pf.greenMax;
 		blueMax = pf.blueMax;
 		bytesPerPixel = pf.bitsPerPixel / 8;
-		bytesPerPixelSignificant = 24 == pf.depth && 32 == pf.bitsPerPixel ? 3 : bytesPerPixel;
+        final long significant = redMax << redShift | greenMax << greenShift | blueMax << blueShift;
+        bytesPerCPixel = pf.depth <= 24 // as in RFB
+//                          || 32 == pf.depth) // UltraVNC use this... :(
+                    && 32 == pf.bitsPerPixel
+                    && ((significant & 0x00ff000000L) == 0 || (significant & 0x000000ffL) == 0)
+                ? 3
+                : bytesPerPixel;
+        bytesPerPixelTight = 24 == pf.depth && 32 == pf.bitsPerPixel ? 3 : bytesPerPixel;
 		buff = new byte[bytesPerPixel];
 		if (0 == pf.bigEndianFlag) {
 			startShift = 0;
@@ -63,7 +71,7 @@
 			startShiftCompact = Math.max(0, pf.depth - 8);
 			addShiftItem = -8;
 		}
-		isTightSpecific = 4==bytesPerPixel && 3==bytesPerPixelSignificant &&
+		isTightSpecific = 4==bytesPerPixel && 3==bytesPerPixelTight &&
 				255 == redMax && 255 == greenMax && 255 == blueMax;
 	}
 
@@ -72,11 +80,11 @@
 	}
 
 	protected int readCompactColor(Reader reader) throws TransportException {
-		return getCompactColor(reader.readBytes(buff, 0, bytesPerPixelSignificant), 0);
+		return getCompactColor(reader.readBytes(buff, 0, bytesPerCPixel), 0);
 	}
 
 	protected int readTightColor(Reader reader) throws TransportException {
-		return getTightColor(reader.readBytes(buff, 0, bytesPerPixelSignificant), 0);
+		return getTightColor(reader.readBytes(buff, 0, bytesPerPixelTight), 0);
 	}
 
 	protected int convertColor(int rawColor) {
@@ -92,7 +100,7 @@
 		comp[2] = (byte) (rawColor >> blueShift & blueMax);
 	}
 
-	protected int getTightColor(byte[] bytes, int offset) {
+	public int getTightColor(byte[] bytes, int offset) {
 		return convertColor(getRawTightColor(bytes, offset));
 	}
 
@@ -100,7 +108,7 @@
 		if (isTightSpecific)
 			return (bytes[offset++] & 0xff)<<16 |
 					(bytes[offset++] & 0xff)<<8 |
-					bytes[offset++] & 0xff;
+					bytes[offset] & 0xff;
 		else
 			return getRawColor(bytes, offset);
 	}
@@ -123,7 +131,7 @@
 		int shift = startShiftCompact;
 		int item = addShiftItem;
 		int rawColor = (bytes[offset++] & 0xff)<<shift;
-		for (int i=1; i<bytesPerPixelSignificant; ++i) {
+		for (int i=1; i< bytesPerCPixel; ++i) {
 			rawColor |= (bytes[offset++] & 0xff)<<(shift+=item);
 		}
 		return convertColor(rawColor);
--- a/src/main/java/com/glavsoft/drawing/Renderer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/drawing/Renderer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -38,291 +38,310 @@
  */
 public abstract class Renderer {
 
-	protected Reader reader;
+    protected Reader reader;
+    private final Object lock = new Object();
 
-	public abstract void drawJpegImage(byte[] bytes, int offset,
-			int jpegBufferLength, FramebufferUpdateRectangle rect);
+    public abstract void drawJpegImage(byte[] bytes, int offset,
+                                       int jpegBufferLength, FramebufferUpdateRectangle rect);
 
-	protected int width;
-	protected int height;
-	protected int bytesPerPixel;
-	protected int bytesPerPixelSignificant;
-	protected int[] pixels;
-	protected SoftCursor cursor;
-	protected PixelFormat pixelFormat;
-	private ColorDecoder colorDecoder;
+    protected int width;
+    protected int height;
+    protected int[] pixels;
+    protected SoftCursor cursor;
+    public PixelFormat pixelFormat;
+    protected ColorDecoder colorDecoder;
 
-	protected void init(Reader reader, int width, int height, PixelFormat pixelFormat) {
-		this.reader = reader;
-		this.width = width;
-		this.height = height;
-		initPixelFormat(pixelFormat);
-		pixels = new int[width * height];
-		Arrays.fill(pixels, 0);
-	}
+    protected void init(Reader reader, int width, int height, PixelFormat pixelFormat) {
+        this.reader = reader;
+        this.width = width;
+        this.height = height;
+        initPixelFormat(pixelFormat);
+        pixels = new int[width * height];
+        Arrays.fill(pixels, 0);
+    }
 
-	public synchronized void initPixelFormat(PixelFormat pixelFormat) {
-		this.pixelFormat = pixelFormat;
-		bytesPerPixel = pixelFormat.bitsPerPixel / 8;
-		bytesPerPixelSignificant =
-				24 == pixelFormat.depth && 32 == pixelFormat.bitsPerPixel ? 3 : bytesPerPixel;
-		colorDecoder = new ColorDecoder(pixelFormat);
-	}
+    public void initPixelFormat(PixelFormat pixelFormat) {
+        synchronized (lock) {
+            this.pixelFormat = pixelFormat;
+            colorDecoder = new ColorDecoder(pixelFormat);
+        }
+    }
 
-	/**
-	 * Draw byte array bitmap data
-	 *
-	 * @param bytes bitmap data
-	 * @param x bitmap x position
-	 * @param y bitmap y position
-	 * @param width bitmap width
-	 * @param height bitmap height
-	 */
-	public void drawBytes(byte[] bytes, int x, int y, int width, int height) {
-		int i = 0;
-		for (int ly = y; ly < y + height; ++ly) {
-			int end = ly * this.width + x + width;
-			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
-				pixels[pixelsOffset] = getPixelColor(bytes, i);
-				i += bytesPerPixel;
-			}
-		}
-	}
+    /**
+     * Draw byte array bitmap data
+     *
+     * @param bytes  bitmap data
+     * @param x      bitmap x position
+     * @param y      bitmap y position
+     * @param width  bitmap width
+     * @param height bitmap height
+     */
+    public void drawBytes(byte[] bytes, int x, int y, int width, int height) {
+        int i = 0;
+        for (int ly = y; ly < y + height; ++ly) {
+            int end = ly * this.width + x + width;
+            for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
+                pixels[pixelsOffset] = getPixelColor(bytes, i);
+                i += colorDecoder.bytesPerPixel;
+            }
+        }
+    }
 
-	/**
-	 * Draw byte array bitmap data (for ZRLE)
-	 */
-	public synchronized int  drawCompactBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
-		int i = offset;
-		for (int ly = y; ly < y + height; ++ly) {
-			int end = ly * this.width + x + width;
-			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
-				pixels[pixelsOffset] = getCompactPixelColor(bytes, i);
-				i += bytesPerPixelSignificant;
-			}
-		}
-		return i - offset;
-	}
+    /**
+     * Draw byte array bitmap data (for ZRLE)
+     */
+    public int drawCompactBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
+        synchronized (lock) {
+            int i = offset;
+            for (int ly = y; ly < y + height; ++ly) {
+                int end = ly * this.width + x + width;
+                for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
+                    pixels[pixelsOffset] = getCompactPixelColor(bytes, i);
+                    i += colorDecoder.bytesPerCPixel;
+                }
+            }
+            return i - offset;
+        }
+    }
 
-	/**
-	 * Draw int (colors) array bitmap data (for ZRLE)
-	 */
-	public synchronized void  drawColoredBitmap(int[] colors, int x, int y, int width, int height) {
-		int i = 0;
-		for (int ly = y; ly < y + height; ++ly) {
-			int end = ly * this.width + x + width;
-			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
-				pixels[pixelsOffset] = colors[i++];
-			}
-		}
-	}
+    /**
+     * Draw int (colors) array bitmap data (for ZRLE)
+     */
+    public void drawColoredBitmap(int[] colors, int x, int y, int width, int height) {
+        synchronized (lock) {
+            int i = 0;
+            for (int ly = y; ly < y + height; ++ly) {
+                int end = ly * this.width + x + width;
+                for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
+                    pixels[pixelsOffset] = colors[i++];
+                }
+            }
+        }
+    }
 
-	/**
-	 * Draw byte array bitmap data (for Tight)
-	 */
-	public synchronized int drawTightBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
-		int i = offset;
-		for (int ly = y; ly < y + height; ++ly) {
-			int end = ly * this.width + x + width;
-			for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
-				pixels[pixelsOffset] = colorDecoder.getTightColor(bytes, i);
-				i += bytesPerPixelSignificant;
-			}
-		}
-		return i - offset;
-	}
+    /**
+     * Draw byte array bitmap data (for Tight)
+     */
+    public int drawTightBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
+        synchronized (lock) {
+            int i = offset;
+            for (int ly = y; ly < y + height; ++ly) {
+                int end = ly * this.width + x + width;
+                for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
+                    pixels[pixelsOffset] = colorDecoder.getTightColor(bytes, i);
+                    i += colorDecoder.bytesPerPixelTight;
+                }
+            }
+            return i - offset;
+        }
+    }
 
-	/**
-	 * Draw byte array bitmap data (from array with plain RGB color components. Assumed: rrrrrrrr gggggggg bbbbbbbb)
-	 */
-	public synchronized void drawUncaliberedRGBLine(byte[] bytes, int x, int y, int width) {
-		int end = y * this.width + x + width;
-		for (int i=3, pixelsOffset = y * this.width + x; pixelsOffset < end; ++pixelsOffset) {
-			pixels[pixelsOffset] =
+    /**
+     * Draw byte array bitmap data (from array with plain RGB color components. Assumed: rrrrrrrr gggggggg bbbbbbbb)
+     */
+    public void drawUncaliberedRGBLine(byte[] bytes, int x, int y, int width) {
+        synchronized (lock) {
+            int end = y * this.width + x + width;
+            for (int i = 3, pixelsOffset = y * this.width + x; pixelsOffset < end; ++pixelsOffset) {
+                pixels[pixelsOffset] =
 //					(0xff & bytes[i++]) << 16 |
 //					(0xff & bytes[i++]) << 8 |
 //					0xff & bytes[i++];
-					(0xff & 255 * (colorDecoder.redMax & bytes[i++]) / colorDecoder.redMax) << 16 |
-					(0xff & 255 * (colorDecoder.greenMax & bytes[i++]) / colorDecoder.greenMax) << 8 |
-					0xff & 255 * (colorDecoder.blueMax & bytes[i++]) / colorDecoder.blueMax;
-		}
-	}
+                        (0xff & 255 * (colorDecoder.redMax & bytes[i++]) / colorDecoder.redMax) << 16 |
+                                (0xff & 255 * (colorDecoder.greenMax & bytes[i++]) / colorDecoder.greenMax) << 8 |
+                                0xff & 255 * (colorDecoder.blueMax & bytes[i++]) / colorDecoder.blueMax;
+            }
+        }
+    }
 
-	/**
-	 * Draw paletted byte array bitmap data
-	 *
-	 * @param buffer bitmap data
-	 * @param rect bitmap location and dimensions
-	 * @param palette colour palette
-	 */
-	public synchronized void drawBytesWithPalette(byte[] buffer, FramebufferUpdateRectangle rect,
-			int[] palette) {
-				// 2 colors
-				if (palette.length == 2) {
-					int dx, dy, n;
-					int i = rect.y * this.width + rect.x;
-					int rowBytes = (rect.width + 7) / 8;
-					byte b;
+    /**
+     * Draw paletted byte array bitmap data
+     *
+     * @param buffer  bitmap data
+     * @param rect    bitmap location and dimensions
+     * @param palette colour palette
+     * @param paletteSize number of colors in palette
+     */
+    public void drawBytesWithPalette(byte[] buffer, FramebufferUpdateRectangle rect,
+                                     int[] palette, int paletteSize) {
+        synchronized (lock) {
+            // 2 colors
+            if (2 == paletteSize) {
+                int dx, dy, n;
+                int i = rect.y * this.width + rect.x;
+                int rowBytes = (rect.width + 7) / 8;
+                byte b;
 
-					for (dy = 0; dy < rect.height; dy++) {
-						for (dx = 0; dx < rect.width / 8; dx++) {
-							b = buffer[dy * rowBytes + dx];
-							for (n = 7; n >= 0; n--) {
-								pixels[i++] = palette[b >> n & 1];
-							}
-						}
-						for (n = 7; n >= 8 - rect.width % 8; n--) {
-							pixels[i++] = palette[buffer[dy * rowBytes + dx] >> n & 1];
-						}
-						i += this.width- rect.width;
-					}
-				} else {
-					// 3..255 colors (assuming bytesPixel == 4).
-					int i = 0;
-					for (int ly =  rect.y; ly < rect.y + rect.height; ++ly) {
-						for (int lx = rect.x; lx < rect.x + rect.width; ++lx) {
-							int pixelsOffset = ly * this.width + lx;
-							pixels[pixelsOffset] = palette[buffer[i++] & 0xFF];
-						}
-					}
-				}
-
-			}
+                for (dy = 0; dy < rect.height; dy++) {
+                    for (dx = 0; dx < rect.width / 8; dx++) {
+                        b = buffer[dy * rowBytes + dx];
+                        for (n = 7; n >= 0; n--) {
+                            pixels[i++] = palette[b >> n & 1];
+                        }
+                    }
+                    for (n = 7; n >= 8 - rect.width % 8; n--) {
+                        pixels[i++] = palette[buffer[dy * rowBytes + dx] >> n & 1];
+                    }
+                    i += this.width - rect.width;
+                }
+            } else {
+                // 3..255 colors (assuming bytesPixel == 4).
+                int i = 0;
+                for (int ly = rect.y; ly < rect.y + rect.height; ++ly) {
+                    for (int lx = rect.x; lx < rect.x + rect.width; ++lx) {
+                        int pixelsOffset = ly * this.width + lx;
+                        pixels[pixelsOffset] = palette[buffer[i++] & 0xFF];
+                    }
+                }
+            }
+        }
+    }
 
-	/**
-	 * Copy rectangle region from one position to another. Regions may be overlapped.
-	 *
-	 * @param srcX source rectangle x position
-	 * @param srcY source rectangle y position
-	 * @param dstRect destination rectangle
-	 */
-	public synchronized void copyRect(int srcX, int srcY, FramebufferUpdateRectangle dstRect) {
-		int startSrcY, endSrcY, dstY, deltaY;
-		if (srcY > dstRect.y) {
-			startSrcY = srcY;
-			endSrcY = srcY + dstRect.height;
-			dstY = dstRect.y;
-			deltaY = +1;
-		} else {
-			startSrcY = srcY + dstRect.height - 1;
-			endSrcY = srcY -1;
-			dstY = dstRect.y + dstRect.height - 1;
-			deltaY = -1;
-		}
-		for (int y = startSrcY; y != endSrcY; y += deltaY) {
-			System.arraycopy(pixels, y * width + srcX,
-					pixels, dstY * width + dstRect.x, dstRect.width);
-			dstY += deltaY;
-		}
-	}
+    /**
+     * Copy rectangle region from one position to another. Regions may be overlapped.
+     *
+     * @param srcX    source rectangle x position
+     * @param srcY    source rectangle y position
+     * @param dstRect destination rectangle
+     */
+    public void copyRect(int srcX, int srcY, FramebufferUpdateRectangle dstRect) {
+        int startSrcY, endSrcY, dstY, deltaY;
+        if (srcY > dstRect.y) {
+            startSrcY = srcY;
+            endSrcY = srcY + dstRect.height;
+            dstY = dstRect.y;
+            deltaY = +1;
+        } else {
+            startSrcY = srcY + dstRect.height - 1;
+            endSrcY = srcY - 1;
+            dstY = dstRect.y + dstRect.height - 1;
+            deltaY = -1;
+        }
+        synchronized (lock) {
+            for (int y = startSrcY; y != endSrcY; y += deltaY) {
+                System.arraycopy(pixels, y * width + srcX,
+                        pixels, dstY * width + dstRect.x, dstRect.width);
+                dstY += deltaY;
+            }
+        }
+    }
 
-	/**
-	 * Fill rectangle region with specified colour
-	 *
-	 * @param color colour to fill with
-	 * @param rect rectangle region posions and dimensions
-	 */
-	public void fillRect(int color, FramebufferUpdateRectangle rect) {
-		fillRect(color, rect.x, rect.y, rect.width, rect.height);
-	}
+    /**
+     * Fill rectangle region with specified colour
+     *
+     * @param color colour to fill with
+     * @param rect  rectangle region positions and dimensions
+     */
+    public void fillRect(int color, FramebufferUpdateRectangle rect) {
+        fillRect(color, rect.x, rect.y, rect.width, rect.height);
+    }
 
-	/**
-	 * Fill rectangle region with specified colour
-	 *
-	 * @param color colour to fill with
-	 * @param x rectangle x position
-	 * @param y rectangle y position
-	 * @param width rectangle width
-	 * @param height rectangle height
-	 */
-	public synchronized void fillRect(int color, int x, int y, int width, int height) {
-		int sy = y * this.width + x;
-		int ey = sy + height * this.width;
-		for (int i = sy; i < ey; i += this.width) {
-			Arrays.fill(pixels, i, i + width, color);
-		}
-	}
+    /**
+     * Fill rectangle region with specified colour
+     *
+     * @param color  colour to fill with
+     * @param x      rectangle x position
+     * @param y      rectangle y position
+     * @param width  rectangle width
+     * @param height rectangle height
+     */
+    public void fillRect(int color, int x, int y, int width, int height) {
+        synchronized (lock) {
+            int sy = y * this.width + x;
+            int ey = sy + height * this.width;
+            for (int i = sy; i < ey; i += this.width) {
+                Arrays.fill(pixels, i, i + width, color);
+            }
+        }
+    }
 
-	/**
-	 * Reads color bytes (PIXEL) from reader, returns int combined RGB
-	 * value consisting of the red component in bits 16-23, the green component
-	 * in bits 8-15, and the blue component in bits 0-7. May be used directly for
-	 * creation awt.Color object
-	 */
-	public int readPixelColor(Reader reader) throws TransportException {
-		return colorDecoder.readColor(reader);
-	}
+    /**
+     * Reads color bytes (PIXEL) from reader, returns int combined RGB
+     * value consisting of the red component in bits 16-23, the green component
+     * in bits 8-15, and the blue component in bits 0-7. May be used directly for
+     * creation awt.Color object
+     */
+    public int readPixelColor(Reader reader) throws TransportException {
+        return colorDecoder.readColor(reader);
+    }
 
-	public int readTightPixelColor(Reader reader) throws TransportException {
-		return colorDecoder.readTightColor(reader);
-	}
+    public int readTightPixelColor(Reader reader) throws TransportException {
+        return colorDecoder.readTightColor(reader);
+    }
 
-	public ColorDecoder getColorDecoder() {
-		return colorDecoder;
-	}
+    public ColorDecoder getColorDecoder() {
+        return colorDecoder;
+    }
 
-	public int getCompactPixelColor(byte[] bytes, int offset) {
-		return colorDecoder.getCompactColor(bytes, offset);
-	}
+    public int getCompactPixelColor(byte[] bytes, int offset) {
+        return colorDecoder.getCompactColor(bytes, offset);
+    }
 
-	public int getPixelColor(byte[] bytes, int offset) {
-		return colorDecoder.getColor(bytes, offset);
-	}
+    public int getPixelColor(byte[] bytes, int offset) {
+        return colorDecoder.getColor(bytes, offset);
+    }
 
-	public int getBytesPerPixel() {
-		return bytesPerPixel;
-	}
+    public int getBytesPerPixel() {
+        return colorDecoder.bytesPerPixel;
+    }
+
+    public int getBytesPerCPixel() {
+        return colorDecoder.bytesPerCPixel;
+    }
 
-	public int getBytesPerPixelSignificant() {
-		return bytesPerPixelSignificant;
-	}
+    public int getBytesPerPixelTight() {
+        return colorDecoder.bytesPerPixelTight;
+    }
+
+    public void fillColorBitmapWithColor(int[] bitmapData, int decodedOffset, int rlength, int color) {
+        while (rlength-- > 0) {
+            bitmapData[decodedOffset++] = color;
+        }
+    }
 
-	public void fillColorBitmapWithColor(int[] bitmapData, int decodedOffset, int rlength, int color) {
-		while (rlength-- > 0) {
-			bitmapData[decodedOffset++] = color;
-		}
-	}
+    /**
+     * Width of rendered image
+     *
+     * @return width
+     */
+    public int getWidth() {
+        return width;
+    }
 
-	/**
-	 * Width of rendered image
-	 *
-	 * @return width
-	 */
-	public int getWidth() {
-		return width;
-	}
+    /**
+     * Height of rendered image
+     *
+     * @return height
+     */
+    public int getHeight() {
+        return height;
+    }
 
-	/**
-	 * Height of rendered image
-	 *
-	 * @return height
-	 */
-	public int getHeight() {
-		return height;
-	}
+    /**
+     * Read and decode cursor image
+     *
+     * @param rect new cursor hot point position and cursor dimensions
+     * @throws TransportException
+     */
+    public void createCursor(int[] cursorPixels, FramebufferUpdateRectangle rect)
+            throws TransportException {
+        synchronized (cursor.getLock()) {
+            cursor.createCursor(cursorPixels, rect.x, rect.y, rect.width, rect.height);
+        }
+    }
 
-	/**
-	 * Read and decode cursor image
-	 *
-	 * @param rect new cursor hot point position and cursor dimensions
-	 * @throws TransportException
-	 */
-	public void createCursor(int[] cursorPixels, FramebufferUpdateRectangle rect)
-		throws TransportException {
-		synchronized (cursor) {
-			cursor.createCursor(cursorPixels, rect.x, rect.y, rect.width, rect.height);
-		}
-	}
+    /**
+     * Read and decode new cursor position
+     *
+     * @param rect cursor position
+     */
+    public void decodeCursorPosition(FramebufferUpdateRectangle rect) {
+        synchronized (cursor.getLock()) {
+            cursor.updatePosition(rect.x, rect.y);
+        }
+    }
 
-	/**
-	 * Read and decode new cursor position
-	 *
-	 * @param rect cursor position
-	 */
-	public void decodeCursorPosition(FramebufferUpdateRectangle rect) {
-		synchronized (cursor) {
-			cursor.updatePosition(rect.x, rect.y);
-		}
-	}
-
+    public Object getLock() {
+        return lock;
+    }
 }
\ No newline at end of file
--- a/src/main/java/com/glavsoft/drawing/SoftCursor.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/drawing/SoftCursor.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -36,8 +36,9 @@
 	public int rX, rY;
 	public int oldRX, oldRY;
 	public int oldWidth, oldHeight;
+    private final Object lock = new Object();
 
-	public SoftCursor(int hotX, int hotY, int width, int height) {
+    public SoftCursor(int hotX, int hotY, int width, int height) {
 		this.hotX = hotX;
 		this.hotY = hotY;
 		oldWidth = this.width = width;
@@ -85,4 +86,7 @@
 
 	protected abstract void createNewCursorImage(int[] cursorPixels, int hotX, int hotY, int width, int height);
 
+    public Object getLock() {
+        return lock;
+    }
 }
\ No newline at end of file
--- a/src/main/java/com/glavsoft/exceptions/AuthenticationFailedException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/AuthenticationFailedException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/ClosedConnectionException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/ClosedConnectionException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/CommonException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/CommonException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/CryptoException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/CryptoException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/FatalException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/FatalException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/ProtocolException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/ProtocolException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/TransportException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/TransportException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/UnsupportedProtocolVersionException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/UnsupportedProtocolVersionException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/exceptions/UnsupportedSecurityTypeException.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/exceptions/UnsupportedSecurityTypeException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/CapabilityContainer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/CapabilityContainer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/ClipboardController.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/ClipboardController.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/IChangeSettingsListener.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/IChangeSettingsListener.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/IPasswordRetriever.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/IPasswordRetriever.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/IRepaintController.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/IRepaintController.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/IRfbSessionListener.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/IRfbSessionListener.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/RfbCapabilityInfo.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/RfbCapabilityInfo.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/ClientCutTextMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/ClientCutTextMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/ClientToServerMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/ClientToServerMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/FramebufferUpdateRequestMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/FramebufferUpdateRequestMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/KeyEventMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/KeyEventMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/PointerEventMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/PointerEventMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/SetEncodingsMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/SetEncodingsMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/client/SetPixelFormatMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/client/SetPixelFormatMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/EncodingType.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/EncodingType.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/PixelFormat.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/PixelFormat.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -83,7 +83,7 @@
     	writer.writeByte(0); // padding bytes
     }
 
-    public static PixelFormat create32bppPixelFormat(int bigEndianFlag) {
+    public static PixelFormat create24bitColorDepthPixelFormat(int bigEndianFlag) {
     	final PixelFormat pixelFormat = new PixelFormat();
     	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
 		pixelFormat.bitsPerPixel = 32;
@@ -101,7 +101,7 @@
     /**
      * specifies 65536 colors, 5bit per Red, 6bit per Green, 5bit per Blue
      */
-    public static PixelFormat create16bppPixelFormat(int bigEndianFlag) {
+    public static PixelFormat create16bitColorDepthPixelFormat(int bigEndianFlag) {
     	final PixelFormat pixelFormat = new PixelFormat();
     	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
 		pixelFormat.bitsPerPixel = 16;
@@ -119,7 +119,7 @@
     /**
      * specifies 256 colors, 2bit per Blue, 3bit per Green & Red
      */
-    public static PixelFormat create8bppBGRPixelFormat(int bigEndianFlag) {
+    public static PixelFormat create8bitColorDepthBGRPixelFormat(int bigEndianFlag) {
     	final PixelFormat pixelFormat = new PixelFormat();
     	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
 		pixelFormat.bitsPerPixel = 8;
@@ -137,7 +137,7 @@
     /**
      * specifies 64 colors, 2bit per Red, Green & Blue
      */
-    public static PixelFormat create6bppPixelFormat(int bigEndianFlag) {
+    public static PixelFormat create6bitColorDepthPixelFormat(int bigEndianFlag) {
     	final PixelFormat pixelFormat = new PixelFormat();
     	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
 		pixelFormat.bitsPerPixel = 8;
--- a/src/main/java/com/glavsoft/rfb/encoding/ServerInitMessage.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/ServerInitMessage.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/ByteBuffer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ByteBuffer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/CopyRectDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/CopyRectDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/Decoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/Decoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/DecodersContainer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/DecodersContainer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/FramebufferUpdateRectangle.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/FramebufferUpdateRectangle.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/HextileDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/HextileDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/RREDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/RREDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/RawDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/RawDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/RichCursorDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/RichCursorDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/TightDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/TightDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,15 +24,15 @@
 
 package com.glavsoft.rfb.encoding.decoder;
 
-import java.util.logging.Logger;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-
 import com.glavsoft.drawing.ColorDecoder;
 import com.glavsoft.drawing.Renderer;
 import com.glavsoft.exceptions.TransportException;
 import com.glavsoft.transport.Reader;
 
+import java.util.logging.Logger;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
 /**
  * Tight protocol extention decoder
  */
@@ -54,17 +54,16 @@
 	Inflater[] decoders;
 
     private int decoderId;
+    private int[] palette;
 
-    final static int tightZlibBufferSize = 512;
-
-	public TightDecoder() {
+    public TightDecoder() {
 		reset();
 	}
 
 	@Override
 	public void decode(Reader reader, Renderer renderer,
 			FramebufferUpdateRectangle rect) throws TransportException {
-		int bytesPerPixel = renderer.getBytesPerPixelSignificant();
+		int bytesPerPixel = renderer.getBytesPerPixelTight();
 
 		/**
 		 * bits
@@ -88,19 +87,12 @@
 			renderer.fillRect(color, rect);
 			break;
 		case JPEG_TYPE:
-			if (bytesPerPixel != 3) {
-//				throw new EncodingException(
-//						"Tight doesn't support JPEG subencoding while depth not equal to 24bpp is used");
-			}
+            assert 3 == bytesPerPixel : "Tight doesn't support JPEG subencoding while depth not equal to 24bpp is used";
 			processJpegType(reader, renderer, rect);
 			break;
 		default:
-			if (compType > JPEG_TYPE) {
-//				throw new EncodingException(
-//						"Compression control byte is incorrect!");
-			} else {
-				processBasicType(compControl, reader, renderer, rect);
-			}
+			assert compType <= JPEG_TYPE : "Compression control byte is incorrect!";
+			processBasicType(compControl, reader, renderer, rect);
 		}
 	}
 
@@ -112,7 +104,7 @@
 		if ((compControl & FILTER_ID_MASK) > 0) { // filter byte presence
 			filterId = reader.readUInt8();
 		}
-		int bytesPerCPixel = renderer.getBytesPerPixelSignificant();
+		int bytesPerCPixel = renderer.getBytesPerPixelTight();
 		int lengthCurrentbpp = bytesPerCPixel * rect.width * rect.height;
 		byte [] buffer;
 		switch (filterId) {
@@ -122,12 +114,12 @@
 			break;
 		case PALETTE_FILTER:
 			int paletteSize = reader.readUInt8() + 1;
-			int[] palette = readPalette(paletteSize, reader, renderer);
+            completePalette(paletteSize, reader, renderer);
 			int dataLength = paletteSize == 2 ?
 				rect.height * ((rect.width + 7) / 8) :
 				rect.width * rect.height;
 			buffer = readTightData(dataLength, reader);
-			renderer.drawBytesWithPalette(buffer, rect, palette);
+			renderer.drawBytesWithPalette(buffer, rect, palette, paletteSize);
 			break;
 		case GRADIENT_FILTER:
 /*
@@ -182,20 +174,19 @@
 	}
 
 	/**
-	 * Read palette from reader
+	 * Complete palette from reader
 	 */
-	private int[] readPalette(int paletteSize, Reader reader, Renderer renderer) throws TransportException {
+	private void completePalette(int paletteSize, Reader reader, Renderer renderer) throws TransportException {
 		/**
 		 * When bytesPerPixel == 1 && paletteSize == 2 read 2 bytes of palette
 		 * When bytesPerPixel == 1 && paletteSize != 2 - error
 		 * When bytesPerPixel == 3 (4) read (paletteSize * 3) bytes of palette
 		 * so use renderer.readPixelColor
 		 */
-		int[] palette = new int[paletteSize];
-		for (int i = 0; i < palette.length; ++i) {
+        if (null == palette) palette = new int[256];
+		for (int i = 0; i < paletteSize; ++i) {
 			palette[i] = renderer.readTightPixelColor(reader);
 		}
-		return palette;
 	}
 
 	/**
@@ -203,7 +194,7 @@
 	 * uncompressed data. When compressed decompresses it.
 	 *
 	 * @param expectedLength expected data length in bytes
-	 * @param reader
+	 * @param reader data source
 	 * @return result data
 	 * @throws TransportException
 	 */
@@ -224,7 +215,7 @@
      * which need to be ignored. Use only first expectedLength bytes.
      *
 	 * @param expectedLength expected data length
-	 * @param reader
+	 * @param reader data source
 	 * @return decompressed data (length == expectedLength) / + followed raw data (ignore, please)
 	 * @throws TransportException
 	 */
@@ -262,7 +253,7 @@
 	 * Lower 7 bit of each byte contains significant data. Max bytes = 3.
 	 * Less significant bytes first order.
 	 *
-	 * @param reader
+	 * @param reader data source
 	 * @return int value
 	 * @throws TransportException
 	 */
@@ -281,7 +272,7 @@
 
 	/**
 	 * Flush (reset) zlib decoders when bits 3, 2, 1, 0 of compControl is set
-	 * @param compControl
+	 * @param compControl control flags
 	 */
 	private void resetDecoders(int compControl) {
 		for (int i=0; i < DECODERS_NUM; ++i) {
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -29,9 +29,11 @@
 import com.glavsoft.transport.Reader;
 
 public class ZRLEDecoder extends ZlibDecoder {
-	private static final int DEFAULT_TILE_SIZE = 64;
+	private static final int MAX_TILE_SIZE = 64;
+    private int[] decodedBitmap;
+    private int[] palette;
 
-	@Override
+    @Override
 	public void decode(Reader reader, Renderer renderer,
 			FramebufferUpdateRectangle rect) throws TransportException {
 		int zippedLength = (int) reader.readUInt32();
@@ -41,18 +43,23 @@
 		int offset = zippedLength;
 		int maxX = rect.x + rect.width;
 		int maxY = rect.y + rect.height;
-		int [] palette = new int [128];
-		for (int tileY = rect.y; tileY < maxY; tileY += DEFAULT_TILE_SIZE) {
-			int tileHeight = Math.min(maxY - tileY, DEFAULT_TILE_SIZE);
+        if (null == palette) {
+            palette = new int [128];
+        }
+        if (null == decodedBitmap) {
+            decodedBitmap = new int[MAX_TILE_SIZE * MAX_TILE_SIZE];
+        }
+		for (int tileY = rect.y; tileY < maxY; tileY += MAX_TILE_SIZE) {
+			int tileHeight = Math.min(maxY - tileY, MAX_TILE_SIZE);
 
-			for (int tileX = rect.x; tileX < maxX; tileX += DEFAULT_TILE_SIZE) {
-				int tileWidth = Math.min(maxX - tileX, DEFAULT_TILE_SIZE);
+			for (int tileX = rect.x; tileX < maxX; tileX += MAX_TILE_SIZE) {
+				int tileWidth = Math.min(maxX - tileX, MAX_TILE_SIZE);
 				int subencoding = bytes[offset++] & 0x0ff;
 				// 128 -plain RLE, 130-255 - Palette RLE
 				boolean isRle = (subencoding & 128) != 0;
 				// 2 to 16 for raw packed palette data, 130 to 255 for Palette RLE (subencoding - 128)
 				int paletteSize = subencoding & 127;
-				offset += readPalette(bytes, offset, renderer, palette, paletteSize);
+				offset += readPalette(bytes, offset, renderer, paletteSize);
 				if (1 == subencoding) { // A solid tile consisting of a single colour
 					renderer.fillRect(palette[0], tileX, tileY, tileWidth, tileHeight);
 					continue;
@@ -61,24 +68,22 @@
 					if (0 == paletteSize) { // subencoding == 128 (or paletteSize == 0) - Plain RLE
 						offset += decodePlainRle(bytes, offset, renderer, tileX, tileY, tileWidth, tileHeight);
 					} else {
-						offset += decodePaletteRle(bytes, offset, renderer, palette, tileX, tileY, tileWidth, tileHeight, paletteSize);
+						offset += decodePaletteRle(bytes, offset, renderer, tileX, tileY, tileWidth, tileHeight);
 					}
 				} else {
 					if (0 == paletteSize) { // subencoding == 0 (or paletteSize == 0) - raw CPIXEL data
 						offset += decodeRaw(bytes, offset, renderer, tileX, tileY, tileWidth, tileHeight);
 					} else {
-						offset += decodePacked(bytes, offset, renderer, palette, paletteSize, tileX, tileY, tileWidth, tileHeight);
+						offset += decodePacked(bytes, offset, renderer, paletteSize, tileX, tileY, tileWidth, tileHeight);
 					}
 				}
 			}
 		}
-
 	}
 
 	private int decodePlainRle(byte[] bytes, int offset, Renderer renderer,
 			int tileX, int tileY, int tileWidth, int tileHeight) {
-		int bytesPerCPixel = renderer.getBytesPerPixelSignificant();
-		int [] decodedBitmap = new int[tileWidth * tileHeight];
+		int bytesPerCPixel = renderer.getBytesPerCPixel();
 		int decodedOffset = 0;
 		int decodedEnd = tileWidth * tileHeight;
 		int index = offset;
@@ -98,8 +103,7 @@
 	}
 
 	private int decodePaletteRle(byte[] bytes, int offset, Renderer renderer,
-			int[] palette, int tileX, int tileY, int tileWidth, int tileHeight, int paletteSize) {
-		int [] decodedBitmap = new int[tileWidth * tileHeight];
+                                 int tileX, int tileY, int tileWidth, int tileHeight) {
 		int decodedOffset = 0;
 		int decodedEnd = tileWidth * tileHeight;
 		int index = offset;
@@ -121,8 +125,7 @@
 	}
 
 	private int decodePacked(byte[] bytes, int offset, Renderer renderer,
-			int[] palette, int paletteSize, int tileX, int tileY, int tileWidth, int tileHeight) {
-		int [] decodedBytes = new int[tileWidth * tileHeight];
+                             int paletteSize, int tileX, int tileY, int tileWidth, int tileHeight) {
 		int bitsPerPalletedPixel = paletteSize > 16 ? 8 : paletteSize > 4 ? 4
 				: paletteSize > 2 ? 2 : 1;
 		int packedOffset = offset;
@@ -140,11 +143,11 @@
 				bitsRemain -= bitsPerPalletedPixel;
 				int index = byteProcessed >> bitsRemain & (1 << bitsPerPalletedPixel) - 1 & 127;
 				int color = palette[index];
-				renderer.fillColorBitmapWithColor(decodedBytes, decodedOffset, 1, color);
+				renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, 1, color);
 				++decodedOffset;
 			}
 		}
-		renderer.drawColoredBitmap(decodedBytes, tileX, tileY, tileWidth, tileHeight);
+		renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
 		return packedOffset - offset;
 	}
 
@@ -153,13 +156,12 @@
 		return renderer.drawCompactBytes(bytes, offset, tileX, tileY, tileWidth, tileHeight);
 	}
 
-	private int readPalette(byte[] bytes, int offset, Renderer renderer,
-			int[] palette, int paletteSize) {
+	private int readPalette(byte[] bytes, int offset, Renderer renderer, int paletteSize) {
+        final int bytesPerCPixel = renderer.getBytesPerCPixel();
 		for (int i=0; i<paletteSize; ++i) {
-			palette[i] = renderer.getCompactPixelColor(bytes,
-					offset + i*renderer.getBytesPerPixelSignificant());
+            palette[i] = renderer.getCompactPixelColor(bytes, offset + i* bytesPerCPixel);
 		}
-		return paletteSize * renderer.getBytesPerPixelSignificant();
+		return paletteSize * bytesPerCPixel;
 	}
 
 }
--- a/src/main/java/com/glavsoft/rfb/encoding/decoder/ZlibDecoder.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ZlibDecoder.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/LocalPointer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/LocalPointer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/MessageQueue.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/MessageQueue.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,10 +24,11 @@
 
 package com.glavsoft.rfb.protocol;
 
+import com.glavsoft.rfb.client.ClientToServerMessage;
+
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
-
-import com.glavsoft.rfb.client.ClientToServerMessage;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author dime at tightvnc.com
@@ -43,8 +44,15 @@
 		queue.offer(message);
 	}
 
+    /**
+     * Retrieves and removes the head of this queue, waiting if necessary until an element becomes available.
+     * Retrieves and removes the head of this queue, waiting up to the certain wait time if necessary for
+     * an element to become available.
+     * @return the head of this queue, or null if the specified waiting time elapses before an element is available
+     * @throws InterruptedException - if interrupted while waiting
+     */
 	public ClientToServerMessage get() throws InterruptedException {
-		return queue.take();
+		return queue.poll(1, TimeUnit.SECONDS);
 	}
 
 }
--- a/src/main/java/com/glavsoft/rfb/protocol/Protocol.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/Protocol.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -42,7 +42,7 @@
 
 public class Protocol implements ProtocolContext, IChangeSettingsListener {
 	private ProtocolState state;
-	private final Logger logger = Logger.getLogger("com.glavsoft.rfb.protocol");
+	private final Logger logger;
 	private final IPasswordRetriever passwordRetriever;
 	private final ProtocolSettings settings;
 	private int fbWidth;
@@ -50,7 +50,7 @@
 	private PixelFormat pixelFormat;
 	private final Reader reader;
 	private final Writer writer;
-	private String remoteDesctopName;
+	private String remoteDesktopName;
 	private MessageQueue messageQueue;
 	private final DecodersContainer decoders;
 	private SenderTask senderTask;
@@ -60,8 +60,10 @@
 	private PixelFormat serverPixelFormat;
 	private Thread senderThread;
 	private Thread receiverThread;
+    private boolean isTight;
+    private String protocolVersion;
 
-	public Protocol(Reader reader, Writer writer,
+    public Protocol(Reader reader, Writer writer,
 			IPasswordRetriever passwordRetriever, ProtocolSettings settings) {
 		this.reader = reader;
 		this.writer = writer;
@@ -70,7 +72,8 @@
 		decoders = new DecodersContainer();
 		decoders.instantiateDecodersWhenNeeded(settings.encodings);
 		state = new HandshakeState(this);
-	}
+        logger = Logger.getLogger(getClass().getName());
+    }
 
 	@Override
 	public void changeStateTo(ProtocolState state) {
@@ -80,7 +83,7 @@
 	public void handshake() throws UnsupportedProtocolVersionException, UnsupportedSecurityTypeException,
 			AuthenticationFailedException, TransportException, FatalException {
 		while (state.next()) {
-			continue;
+			// continue;
 		}
 		this.messageQueue = new MessageQueue();
 	}
@@ -100,12 +103,12 @@
 
 	@Override
 	public String getRemoteDesktopName() {
-		return remoteDesctopName;
+		return remoteDesktopName;
 	}
 
 	@Override
 	public void setRemoteDesktopName(String name) {
-		remoteDesctopName = name;
+		remoteDesktopName = name;
 	}
 
 	@Override
@@ -138,12 +141,7 @@
 		return settings;
 	}
 
-	@Override
-	public Logger getLogger() {
-		return logger;
-	}
-
-	@Override
+    @Override
 	public Writer getWriter() {
 		return writer;
 	}
@@ -169,14 +167,14 @@
 			IRepaintController repaintController, ClipboardController clipboardController) {
 		this.rfbSessionListener = rfbSessionListener;
 		this.repaintController = repaintController;
-//		if (settings.getBitsPerPixel() == 0) {
-//			settings.setBitsPerPixel(pixelFormat.bitsPerPixel); // the same the server sent when not initialized yet
+//		if (settings.getColorDepth() == 0) {
+//			settings.setColorDepth(pixelFormat.depth); // the same the server sent when not initialized yet
 //		}
 		serverPixelFormat = pixelFormat;
-		serverPixelFormat.trueColourFlag = 1; // correct flag - we don't support color maps
+        correctServerPixelFormat();
 		setPixelFormat(createPixelFormat(settings));
 		sendMessage(new SetPixelFormatMessage(pixelFormat));
-		logger.fine("sent: "+pixelFormat);
+		logger.fine("sent: " + pixelFormat);
 
 		sendSupportedEncodingsMessage(settings);
 		settings.addListener(this); // to support pixel format (color depth), and encodings changes
@@ -184,18 +182,33 @@
 
 		sendRefreshMessage();
 		senderTask = new SenderTask(messageQueue, writer, this);
-		senderThread = new Thread(senderTask);
+		senderThread = new Thread(senderTask, "RfbSenderTask");
 		senderThread.start();
 		decoders.resetDecoders();
 		receiverTask = new ReceiverTask(
 				reader, repaintController,
 				clipboardController,
 				decoders, this);
-		receiverThread = new Thread(receiverTask);
+		receiverThread = new Thread(receiverTask, "RfbReceiverTask");
 		receiverThread.start();
 	}
 
-	@Override
+    private void correctServerPixelFormat() {
+        // correct true color flag - we don't support color maps, so always set it up
+        serverPixelFormat.trueColourFlag = 1;
+        // correct .depth to use actual depth 24 instead of incorrect 32, used by ex. UltraVNC server, that cause
+        // protocol incompatibility in ZRLE encoding
+        final long significant = serverPixelFormat.redMax << serverPixelFormat.redShift |
+                serverPixelFormat.greenMax << serverPixelFormat.greenShift |
+                serverPixelFormat.blueMax << serverPixelFormat.blueShift;
+        if (32 == serverPixelFormat.bitsPerPixel &&
+                ((significant & 0x00ff000000L) == 0 || (significant & 0x000000ffL) == 0) &&
+                32 == serverPixelFormat.depth) {
+            serverPixelFormat.depth = 24;
+        }
+    }
+
+    @Override
 	public void sendMessage(ClientToServerMessage message) {
 		messageQueue.put(message);
 	}
@@ -212,22 +225,22 @@
 	 */
 	private PixelFormat createPixelFormat(ProtocolSettings settings) {
 		int serverBigEndianFlag = serverPixelFormat.bigEndianFlag;
-		switch (settings.getBitsPerPixel()) {
-		case ProtocolSettings.BPP_32:
-			return PixelFormat.create32bppPixelFormat(serverBigEndianFlag);
-		case ProtocolSettings.BPP_16:
-			return PixelFormat.create16bppPixelFormat(serverBigEndianFlag);
-		case ProtocolSettings.BPP_8:
-			return PixelFormat.create8bppBGRPixelFormat(serverBigEndianFlag);
-		case ProtocolSettings.BPP_6:
-			return PixelFormat.create6bppPixelFormat(serverBigEndianFlag);
-		case ProtocolSettings.BPP_3:
+		switch (settings.getColorDepth()) {
+		case ProtocolSettings.COLOR_DEPTH_24:
+			return PixelFormat.create24bitColorDepthPixelFormat(serverBigEndianFlag);
+		case ProtocolSettings.COLOR_DEPTH_16:
+			return PixelFormat.create16bitColorDepthPixelFormat(serverBigEndianFlag);
+		case ProtocolSettings.COLOR_DEPTH_8:
+			return PixelFormat.create8bitColorDepthBGRPixelFormat(serverBigEndianFlag);
+		case ProtocolSettings.COLOR_DEPTH_6:
+			return PixelFormat.create6bitColorDepthPixelFormat(serverBigEndianFlag);
+		case ProtocolSettings.COLOR_DEPTH_3:
 			return PixelFormat.create3bppPixelFormat(serverBigEndianFlag);
-		case ProtocolSettings.BPP_SERVER_SETTINGS:
+		case ProtocolSettings.COLOR_DEPTH_SERVER_SETTINGS:
 			return serverPixelFormat;
 		default:
 			// unsupported bpp, use default
-			return PixelFormat.create32bppPixelFormat(serverBigEndianFlag);
+			return PixelFormat.create24bitColorDepthPixelFormat(serverBigEndianFlag);
 		}
 	}
 
@@ -237,7 +250,7 @@
 		if (settings.isChangedEncodings()) {
 			sendSupportedEncodingsMessage(settings);
 		}
-		if (settings.changedBitsPerPixel() && receiverTask != null) {
+		if (settings.isChangedColorDepth() && receiverTask != null) {
 			receiverTask.queueUpdatePixelFormat(createPixelFormat(settings));
 		}
 	}
@@ -275,4 +288,24 @@
 		}
 	}
 
+    @Override
+    public void setTight(boolean isTight) {
+        this.isTight = isTight;
+    }
+
+    @Override
+    public boolean isTight() {
+        return isTight;
+    }
+
+    @Override
+    public void setProtocolVersion(String protocolVersion) {
+        this.protocolVersion = protocolVersion;
+    }
+
+    @Override
+    public String getProtocolVersion() {
+        return protocolVersion;
+    }
+
 }
--- a/src/main/java/com/glavsoft/rfb/protocol/ProtocolContext.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/ProtocolContext.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -41,9 +41,7 @@
 
 	ProtocolSettings getSettings();
 
-	Logger getLogger();
-
-	Writer getWriter();
+    Writer getWriter();
 	Reader getReader();
 
 	int getFbWidth();
@@ -65,4 +63,10 @@
 	
 	void cleanUpSession(String message);
 
+    void setTight(boolean isTight);
+	boolean isTight();
+
+    void setProtocolVersion(String protocolVersion);
+    String getProtocolVersion();
+
 }
\ No newline at end of file
--- a/src/main/java/com/glavsoft/rfb/protocol/ProtocolSettings.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/ProtocolSettings.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -31,6 +31,7 @@
 import com.glavsoft.rfb.encoding.EncodingType;
 import com.glavsoft.rfb.protocol.auth.SecurityType;
 
+import java.io.Serializable;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
@@ -39,23 +40,26 @@
 /**
  * Protocol Settings class
  */
-public class ProtocolSettings implements Cloneable {
+public class ProtocolSettings implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     private static final EncodingType DEFAULT_PREFERRED_ENCODING = EncodingType.TIGHT;
 	public static final int DEFAULT_JPEG_QUALITY = 6;
 	private static final int DEFAULT_COMPRESSION_LEVEL = -6;
 
-	// bits per pixel constants
-	public static final int BPP_32 = 32;
-	public static final int BPP_16 = 16;
-	public static final int BPP_8 = 8;
-	public static final int BPP_6 = 6;
-	public static final int BPP_3 = 3;
+	// color depth constants
+	public static final int COLOR_DEPTH_32 = 32;
+    public static final int COLOR_DEPTH_24 = 24;
+	public static final int COLOR_DEPTH_16 = 16;
+	public static final int COLOR_DEPTH_8 = 8;
+	public static final int COLOR_DEPTH_6 = 6;
+	public static final int COLOR_DEPTH_3 = 3;
 
-	public static final int BPP_SERVER_SETTINGS = 0;
+	public static final int COLOR_DEPTH_SERVER_SETTINGS = 0;
 
-	private static final int DEFAULT_BITS_PER_PIXEL = BPP_32;
+	private static final int DEFAULT_COLOR_DEPTH = COLOR_DEPTH_24;
 
-	public static final int CHANGED_VIEW_ONLY                   = 1 << 0;
+	public static final int CHANGED_VIEW_ONLY                   = 1; // 1 << 0;
 	public static final int CHANGED_ENCODINGS                   = 1 << 1;
 	public static final int CHANGED_ALLOW_COPY_RECT             = 1 << 2;
 	public static final int CHANGED_SHOW_REMOTE_CURSOR          = 1 << 3;
@@ -64,9 +68,10 @@
 	public static final int CHANGED_JPEG_QUALITY                = 1 << 6;
 	public static final int CHANGED_ALLOW_CLIPBOARD_TRANSFER    = 1 << 7;
 	public static final int CHANGED_CONVERT_TO_ASCII            = 1 << 8;
-	public static final int CHANGED_BITS_PER_PIXEL              = 1 << 9;
+	public static final int CHANGED_COLOR_DEPTH = 1 << 9;
+    public static final int CHANGED_SHARED                      = 1 << 10;
 
-	private int changedSettingsMask;
+	private transient int changedSettingsMask;
 
     private boolean sharedFlag;
     private boolean viewOnly;
@@ -78,78 +83,77 @@
     private int jpegQuality;
 	private boolean allowClipboardTransfer;
 	private boolean convertToAscii;
-	private int bitsPerPixel;
-
-	private String protocolVersion;
-	private boolean isTight;
+	private int colorDepth;
 
-	public LinkedHashSet<EncodingType> encodings;
+	public transient LinkedHashSet<EncodingType> encodings;
+	private transient final List<IChangeSettingsListener> listeners;
 
-	private final List<IChangeSettingsListener> listeners;
-
-    public CapabilityContainer
+    public transient CapabilityContainer
 		tunnelingCapabilities,
 		authCapabilities,
 		serverMessagesCapabilities,
 		clientMessagesCapabilities,
 		encodingTypesCapabilities;
-	private String remoteCharsetName;
+	private transient String remoteCharsetName;
 
 	public static ProtocolSettings getDefaultSettings() {
     	ProtocolSettings settings = new ProtocolSettings();
 	    settings.initKnownAuthCapabilities(settings.authCapabilities);
 	    settings.initKnownEncodingTypesCapabilities(settings.encodingTypesCapabilities);
-
-        settings.sharedFlag = true;
-        settings.viewOnly = false;
-        settings.showRemoteCursor = true;
-        settings.mouseCursorTrack = LocalPointer.ON;
-        settings.preferredEncoding = DEFAULT_PREFERRED_ENCODING;
-        settings.allowCopyRect = true;
-        settings.compressionLevel = DEFAULT_COMPRESSION_LEVEL;
-        settings.jpegQuality = DEFAULT_JPEG_QUALITY;
-        settings.convertToAscii = false;
-        settings.allowClipboardTransfer = true;
-        settings.bitsPerPixel = 0;//DEFAULT_BITS_PER_PIXEL;
-        settings.refine();
-	    settings.changedSettingsMask = 0;
         return settings;
     }
 
 	private ProtocolSettings() {
-		listeners = new LinkedList<IChangeSettingsListener>();
+        sharedFlag = true;
+        viewOnly = false;
+        showRemoteCursor = true;
+        mouseCursorTrack = LocalPointer.ON;
+        preferredEncoding = DEFAULT_PREFERRED_ENCODING;
+        allowCopyRect = true;
+        compressionLevel = DEFAULT_COMPRESSION_LEVEL;
+        jpegQuality = DEFAULT_JPEG_QUALITY;
+        convertToAscii = false;
+        allowClipboardTransfer = true;
+        colorDepth = COLOR_DEPTH_SERVER_SETTINGS;
+        refine();
+
+        listeners = new LinkedList<IChangeSettingsListener>();
 		tunnelingCapabilities = new CapabilityContainer();
 		authCapabilities = new CapabilityContainer();
 		serverMessagesCapabilities = new CapabilityContainer();
 		clientMessagesCapabilities = new CapabilityContainer();
 		encodingTypesCapabilities = new CapabilityContainer();
 		changedSettingsMask = 0;
-	}
+   	}
 
-	private ProtocolSettings(ProtocolSettings s) {
+	public ProtocolSettings(ProtocolSettings s) {
 		this();
+        copyDataFrom(s);
+        changedSettingsMask = s.changedSettingsMask;
+        encodings = s.encodings;
+    }
 
-		changedSettingsMask = s.changedSettingsMask;
+    public void copyDataFrom(ProtocolSettings s) {
+        copyDataFrom(s, 0);
+    }
 
-        sharedFlag = s.sharedFlag;
-        viewOnly = s.viewOnly;
-        preferredEncoding = s.preferredEncoding;
-        allowCopyRect = s.allowCopyRect;
-        showRemoteCursor = s.showRemoteCursor;
-        mouseCursorTrack = s.mouseCursorTrack;
-        compressionLevel = s.compressionLevel;
-        jpegQuality = s.jpegQuality;
-		allowClipboardTransfer = s.allowClipboardTransfer;
-		convertToAscii = s.convertToAscii;
-		bitsPerPixel = s.bitsPerPixel;
+    public void copyDataFrom(ProtocolSettings s, int mask) {
+        if (null == s) return;
+        if ((mask & CHANGED_SHARED) == 0) setSharedFlag(s.sharedFlag);
+        if ((mask & CHANGED_VIEW_ONLY) == 0) setViewOnly(s.viewOnly);
+        if ((mask & CHANGED_ALLOW_COPY_RECT) == 0) setAllowCopyRect(s.allowCopyRect);
+        if ((mask & CHANGED_SHOW_REMOTE_CURSOR) == 0) setShowRemoteCursor(s.showRemoteCursor);
+        if ((mask & CHANGED_ALLOW_CLIPBOARD_TRANSFER) == 0) setAllowClipboardTransfer(s.allowClipboardTransfer);
 
-		encodings = s.encodings;
+        if ((mask & CHANGED_MOUSE_CURSOR_TRACK) == 0) setMouseCursorTrack(s.mouseCursorTrack);
+        if ((mask & CHANGED_COMPRESSION_LEVEL) == 0) setCompressionLevel(s.compressionLevel);
+        if ((mask & CHANGED_JPEG_QUALITY) == 0) setJpegQuality(s.jpegQuality);
+        if ((mask & CHANGED_CONVERT_TO_ASCII) == 0) setConvertToAscii(s.convertToAscii);
+        if ((mask & CHANGED_COLOR_DEPTH) == 0) setColorDepth(s.colorDepth);
+        if ((mask & CHANGED_ENCODINGS) == 0) setPreferredEncoding(s.preferredEncoding);
+    }
 
-		protocolVersion = s.protocolVersion;
-		isTight = s.isTight;
-	}
-
-	private void initKnownAuthCapabilities(CapabilityContainer cc) {
+    private void initKnownAuthCapabilities(CapabilityContainer cc) {
 		cc.addEnabled(SecurityType.NONE_AUTHENTICATION.getId(),
 				RfbCapabilityInfo.VENDOR_STANDARD, RfbCapabilityInfo.AUTHENTICATION_NO_AUTH);
 		cc.addEnabled(SecurityType.VNC_AUTHENTICATION.getId(),
@@ -195,7 +199,10 @@
 	}
 
 	public void setSharedFlag(boolean sharedFlag) {
-		this.sharedFlag = sharedFlag;
+        if (this.sharedFlag != sharedFlag) {
+		    this.sharedFlag = sharedFlag;
+            changedSettingsMask |= CHANGED_SHARED;
+        }
 	}
 
 	public boolean isViewOnly() {
@@ -214,33 +221,36 @@
 
 	}
 
-	public int getBitsPerPixel() {
-		return bitsPerPixel;
+	public int getColorDepth() {
+		return colorDepth;
 	}
 
 	/**
-	 * Set bpp only in 3, 6, 8, 16, 32. When bpp is wrong, it resets to {@link #DEFAULT_BITS_PER_PIXEL}
+	 * Set depth only in 3, 6, 8, 16, 32. When depth is wrong, it resets to {@link #DEFAULT_COLOR_DEPTH}
 	 */
-	public void setBitsPerPixel(int bpp) {
-		if (bitsPerPixel != bpp) {
-			changedSettingsMask |= CHANGED_BITS_PER_PIXEL;
-			switch (bpp) {
-			case BPP_32:
-			case BPP_16:
-			case BPP_8:
-			case BPP_6:
-			case BPP_3:
-			case BPP_SERVER_SETTINGS:
-				bitsPerPixel = bpp;
+	public void setColorDepth(int depth) {
+		if (colorDepth != depth) {
+			changedSettingsMask |= CHANGED_COLOR_DEPTH;
+			switch (depth) {
+            case COLOR_DEPTH_32:
+                colorDepth = COLOR_DEPTH_24;
+                break;
+			case COLOR_DEPTH_24:
+			case COLOR_DEPTH_16:
+			case COLOR_DEPTH_8:
+			case COLOR_DEPTH_6:
+			case COLOR_DEPTH_3:
+			case COLOR_DEPTH_SERVER_SETTINGS:
+				colorDepth = depth;
 				break;
 			default:
-				bitsPerPixel = DEFAULT_BITS_PER_PIXEL;
+				colorDepth = DEFAULT_COLOR_DEPTH;
 			}
 			refine();
 		}
 	}
 
-	private void refine() {
+	public void refine() {
 		LinkedHashSet<EncodingType> encodings = new LinkedHashSet<EncodingType>();
 		if (EncodingType.RAW_ENCODING == preferredEncoding) {
 			// when RAW selected send no ordinary encodings so only default RAW encoding will be enabled
@@ -252,7 +262,7 @@
 						EncodingType.COMPRESS_LEVEL_0.getId() + compressionLevel));
 			}
 			if (jpegQuality > 0 && jpegQuality < 10 &&
-					(bitsPerPixel == BPP_32 || bitsPerPixel == BPP_SERVER_SETTINGS)) {
+					(colorDepth == COLOR_DEPTH_24 || colorDepth == COLOR_DEPTH_SERVER_SETTINGS)) {
 				encodings.add(EncodingType.byId(
 						EncodingType.JPEG_QUALITY_LEVEL_0.getId() + jpegQuality));
 			}
@@ -295,7 +305,8 @@
 	}
 
 	public void fireListeners() {
-		final SettingsChangedEvent event = new SettingsChangedEvent(new ProtocolSettings(this));
+        if (null == listeners) return;
+        final SettingsChangedEvent event = new SettingsChangedEvent(new ProtocolSettings(this));
 		changedSettingsMask = 0;
 		for (IChangeSettingsListener listener : listeners) {
 			listener.settingsChanged(event);
@@ -388,22 +399,6 @@
 		return allowClipboardTransfer;
 	}
 
-	public void setTight(boolean isTight) {
-		this.isTight = isTight;
-	}
-
-	public boolean isTight() {
-		return isTight;
-	}
-
-	public void setProtocolVersion(String protocolVersion) {
-		this.protocolVersion = protocolVersion;
-	}
-
-	public String getProtocolVersion() {
-		return protocolVersion;
-	}
-
 	public boolean isConvertToAscii() {
 		return convertToAscii;
 	}
@@ -419,8 +414,8 @@
 		return (changedSettingsMask & CHANGED_ENCODINGS) == CHANGED_ENCODINGS;
 	}
 
-	public boolean changedBitsPerPixel() {
-		return (changedSettingsMask & CHANGED_BITS_PER_PIXEL) == CHANGED_BITS_PER_PIXEL;
+	public boolean isChangedColorDepth() {
+		return (changedSettingsMask & CHANGED_COLOR_DEPTH) == CHANGED_COLOR_DEPTH;
 	}
 
 	public void setRemoteCharsetName(String remoteCharsetName) {
@@ -431,4 +426,20 @@
 		return remoteCharsetName;
 	}
 
+    @Override
+    public String toString() {
+        return "ProtocolSettings{" +
+                "sharedFlag=" + sharedFlag +
+                ", viewOnly=" + viewOnly +
+                ", preferredEncoding=" + preferredEncoding +
+                ", allowCopyRect=" + allowCopyRect +
+                ", showRemoteCursor=" + showRemoteCursor +
+                ", mouseCursorTrack=" + mouseCursorTrack +
+                ", compressionLevel=" + compressionLevel +
+                ", jpegQuality=" + jpegQuality +
+                ", allowClipboardTransfer=" + allowClipboardTransfer +
+                ", convertToAscii=" + convertToAscii +
+                ", colorDepth=" + colorDepth +
+                '}';
+    }
 }
--- a/src/main/java/com/glavsoft/rfb/protocol/ReceiverTask.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/ReceiverTask.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -105,8 +105,8 @@
 					logger.severe("Unsupported server message. Id = " + messageId);
 				}
 			} catch (TransportException e) {
-				logger.severe("Close session: " + e.getMessage());
 				if (isRunning) {
+                    logger.severe("Close session: " + e.getMessage());
 					context.cleanUpSession("Connection closed.");
 				}
 				stopTask();
@@ -119,7 +119,7 @@
 			} catch (CommonException e) {
 				logger.severe(e.getMessage());
 				if (isRunning) {
-					context.cleanUpSession("Connection closed.");
+					context.cleanUpSession("Connection closed..");
 				}
 				stopTask();
 			} catch (Throwable te) {
@@ -173,7 +173,7 @@
 			} else if (rect.getEncodingType() == EncodingType.DESKTOP_SIZE) {
 				fullscreenFbUpdateIncrementalRequest =
 					new FramebufferUpdateRequestMessage(0, 0, rect.width, rect.height, true);
-				synchronized (renderer) {
+				synchronized (renderer.getLock()) {
 					renderer = repaintController.createRenderer(reader, rect.width, rect.height,
 							context.getPixelFormat());
 				}
--- a/src/main/java/com/glavsoft/rfb/protocol/SenderTask.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/SenderTask.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -46,7 +46,7 @@
 	 * request
 	 * @param messageQueue queue to poll messages
 	 * @param writer writer to send messages out
-	 * @param protocolContext
+	 * @param protocolContext protocol
 	 */
 	public SenderTask(MessageQueue messageQueue, Writer writer, ProtocolContext protocolContext) {
 		this.queue = messageQueue;
@@ -67,7 +67,7 @@
 			} catch (InterruptedException e) {
 				// nop
 			} catch (TransportException e) {
-				Logger.getLogger("com.glavsoft.rfb.protocol").severe("Close session: " + e.getMessage());
+				Logger.getLogger(getClass().getName()).severe("Close session: " + e.getMessage());
 				if (isRunning) {
 					protocolContext.cleanUpSession("Connection closed");
 				}
--- a/src/main/java/com/glavsoft/rfb/protocol/auth/AuthHandler.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/AuthHandler.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/auth/NoneAuthentication.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/NoneAuthentication.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/auth/SecurityType.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/SecurityType.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/auth/TightAuthentication.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/TightAuthentication.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/auth/VncAuthentication.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/VncAuthentication.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -50,6 +50,7 @@
 	throws TransportException, FatalException {
 		byte [] challenge = reader.readBytes(16);
 		String password = passwordRetriever.getPassword();
+        if (null == password) return false;
 		byte [] key = new byte[8];
         System.arraycopy(password.getBytes(), 0, key, 0, Math.min(key.length, password.getBytes().length));
 	    writer.write(encrypt(challenge, key));
@@ -58,8 +59,6 @@
 
   /**
 	 * Encript challenge by key using DES
-	 * @param challenge
-	 * @param key
 	 * @return encripted bytes
 	 * @throws CryptoException on problem with DES algorithm support or smth about
 	 */
@@ -70,8 +69,7 @@
 		    SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
 		    Cipher desCipher = Cipher.getInstance("DES/ECB/NoPadding");
 		    desCipher.init(Cipher.ENCRYPT_MODE, secretKey);
-		    byte[] textEncrypted = desCipher.doFinal(challenge);
-		    return textEncrypted;
+            return desCipher.doFinal(challenge);
 		} catch (NoSuchAlgorithmException e) {
 			throw new CryptoException("Cannot encrypt challenge", e);
 		} catch (NoSuchPaddingException e) {
--- a/src/main/java/com/glavsoft/rfb/protocol/state/AuthenticationState.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/AuthenticationState.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,12 +24,7 @@
 
 package com.glavsoft.rfb.protocol.state;
 
-import com.glavsoft.exceptions.AuthenticationFailedException;
-import com.glavsoft.exceptions.ClosedConnectionException;
-import com.glavsoft.exceptions.FatalException;
-import com.glavsoft.exceptions.TransportException;
-import com.glavsoft.exceptions.UnsupportedProtocolVersionException;
-import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+import com.glavsoft.exceptions.*;
 import com.glavsoft.rfb.protocol.ProtocolContext;
 import com.glavsoft.rfb.protocol.auth.AuthHandler;
 
@@ -62,7 +57,7 @@
 			checkSecurityResult();
 		}
 		changeStateTo(isTight ? new InitTightState(context) : new InitState(context));
-		context.getSettings().setTight(isTight);
+		context.setTight(isTight);
 	}
 
 	/**
--- a/src/main/java/com/glavsoft/rfb/protocol/state/HandshakeState.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/HandshakeState.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -28,6 +28,7 @@
 import com.glavsoft.exceptions.UnsupportedProtocolVersionException;
 import com.glavsoft.rfb.protocol.ProtocolContext;
 
+import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -57,7 +58,7 @@
 
 	private void handshake() throws TransportException, UnsupportedProtocolVersionException {
 		String protocolString = reader.readString(PROTOCOL_STRING_LENGTH);
-		logger.info("Server sent protocol string: " + protocolString.substring(0, protocolString.length() - 1));
+        Logger.getLogger(getClass().getName()).info("Server sent protocol string: " + protocolString.substring(0, protocolString.length() - 1));
 		Pattern pattern = Pattern.compile(PROTOCOL_STRING_REGEXP);
 		final Matcher matcher = pattern.matcher(protocolString);
 		if ( ! matcher.matches())
@@ -76,21 +77,21 @@
 
 		if (minor >= MIN_SUPPORTED_VERSION_MINOR && minor < 7) {
 			changeStateTo(new SecurityType33State(context));
-			context.getSettings().setProtocolVersion(PROTOCOL_VERSION_3_3);
+			context.setProtocolVersion(PROTOCOL_VERSION_3_3);
 			minor = 3;
 		} else if (7 == minor) {
 			changeStateTo(new SecurityType37State(context));
-			context.getSettings().setProtocolVersion(PROTOCOL_VERSION_3_7);
+			context.setProtocolVersion(PROTOCOL_VERSION_3_7);
 			minor = 7;
 		} else if (minor >= MAX_SUPPORTED_VERSION_MINOR) {
 			changeStateTo(new SecurityTypeState(context));
-			context.getSettings().setProtocolVersion(PROTOCOL_VERSION_3_8);
+			context.setProtocolVersion(PROTOCOL_VERSION_3_8);
 			minor = 8;
 		} else
 			throw new UnsupportedProtocolVersionException(
 					"Unsupported protocol version: " + protocolString);
 		writer.write(("RFB 00" + major + ".00" + minor + "\n").getBytes());
-		logger.info("Set protocol version to: " + context.getSettings().getProtocolVersion());
+        Logger.getLogger(getClass().getName()).info("Set protocol version to: " + context.getProtocolVersion());
 	}
 
 }
--- a/src/main/java/com/glavsoft/rfb/protocol/state/InitState.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/InitState.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -33,6 +33,8 @@
 import com.glavsoft.rfb.protocol.ProtocolContext;
 import com.glavsoft.rfb.protocol.ProtocolSettings;
 
+import java.util.logging.Logger;
+
 /**
  * ClientInit
  *
@@ -79,13 +81,12 @@
 		context.setFbWidth(serverInitMessage.getFrameBufferWidth());
 		context.setFbHeight(serverInitMessage.getFrameBufferHeight());
 		context.setRemoteDesktopName(serverInitMessage.getName());
-		logger.fine(serverInitMessage.toString());
+        Logger.getLogger(getClass().getName()).fine(serverInitMessage.toString());
 	}
 
 	protected ServerInitMessage getServerInitMessage() throws TransportException {
 		writer.write(context.getSettings().getSharedFlag());
-		ServerInitMessage serverInitMessage = new ServerInitMessage(reader);
-		return serverInitMessage;
+        return new ServerInitMessage(reader);
 	}
 
 
--- a/src/main/java/com/glavsoft/rfb/protocol/state/InitTightState.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/InitTightState.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/state/ProtocolState.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/ProtocolState.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,26 +24,18 @@
 
 package com.glavsoft.rfb.protocol.state;
 
-import java.util.logging.Logger;
-
-import com.glavsoft.exceptions.AuthenticationFailedException;
-import com.glavsoft.exceptions.FatalException;
-import com.glavsoft.exceptions.TransportException;
-import com.glavsoft.exceptions.UnsupportedProtocolVersionException;
-import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+import com.glavsoft.exceptions.*;
 import com.glavsoft.rfb.protocol.ProtocolContext;
 import com.glavsoft.transport.Reader;
 import com.glavsoft.transport.Writer;
 
 abstract public class ProtocolState {
 	protected ProtocolContext context;
-	protected Logger logger;
 	protected Reader reader;
 	protected Writer writer;
 
 	public ProtocolState(ProtocolContext context) {
 		this.context = context;
-		this.logger = context.getLogger();
 		this.reader = context.getReader();
 		this.writer = context.getWriter();
 	}
--- a/src/main/java/com/glavsoft/rfb/protocol/state/SecurityType33State.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/SecurityType33State.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -29,6 +29,8 @@
 import com.glavsoft.rfb.protocol.ProtocolContext;
 import com.glavsoft.rfb.protocol.auth.AuthHandler;
 
+import java.util.logging.Logger;
+
 public class SecurityType33State extends SecurityType37State {
 
 	public SecurityType33State(ProtocolContext context) {
@@ -38,9 +40,9 @@
 	@Override
 	protected void negotiateAboutSecurityType()
 			throws TransportException, UnsupportedSecurityTypeException {
-		logger.info("Get Security Type");
+        Logger.getLogger(getClass().getName()).info("Get Security Type");
 		int type = reader.readInt32();
-		logger.info("Type received: " + type);
+		Logger.getLogger(getClass().getName()).info("Type received: " + type);
 		if (0 == type)
 			// throw exception with reason
 			throw new UnsupportedSecurityTypeException(reader.readString());
@@ -48,7 +50,7 @@
 					context.getSettings().authCapabilities);
 		if (typeSelected != null) {
 			setUseSecurityResult(typeSelected);
-			logger.info("Type accepted: " + typeSelected.getName());
+			Logger.getLogger(getClass().getName()).info("Type accepted: " + typeSelected.getName());
 		} else
 			throw new UnsupportedSecurityTypeException(
 					"No security types supported. Server sent '" +
--- a/src/main/java/com/glavsoft/rfb/protocol/state/SecurityType37State.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/SecurityType37State.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/rfb/protocol/state/SecurityTypeState.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/SecurityTypeState.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -33,6 +33,8 @@
 import com.glavsoft.rfb.protocol.auth.SecurityType;
 import com.glavsoft.utils.Strings;
 
+import java.util.logging.Logger;
+
 public class SecurityTypeState extends ProtocolState {
 
 	public SecurityTypeState(ProtocolContext context) {
@@ -52,13 +54,13 @@
 			// throw exception with reason
 			throw new UnsupportedSecurityTypeException(reader.readString());
 		byte[] secTypes = reader.readBytes(secTypesNum);
-		logger.info("Security Types received (" + secTypesNum + "): "
-				+ Strings.toString(secTypes));
+        Logger.getLogger(getClass().getName()).info("Security Types received (" + secTypesNum + "): "
+                + Strings.toString(secTypes));
 		AuthHandler typeSelected = selectAuthHandler(
 				secTypes, context.getSettings().authCapabilities);
 		setUseSecurityResult(typeSelected);
 		writer.writeByte(typeSelected.getId());
-		logger.info("Security Type accepted: " + typeSelected.getName());
+        Logger.getLogger(getClass().getName()).info("Security Type accepted: " + typeSelected.getName());
 
 		changeStateTo(new AuthenticationState(context, typeSelected));
 	}
--- a/src/main/java/com/glavsoft/transport/Reader.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/transport/Reader.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -32,6 +32,7 @@
 
 public class Reader {
 	final static Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
+	final static Charset UTF8 = Charset.forName("UTF-8");
 	private final DataInputStream is;
 
 	public Reader(InputStream is) {
@@ -80,7 +81,17 @@
 		} catch (EOFException e) {
 			throw new ClosedConnectionException(e);
 		} catch (IOException e) {
-			throw new TransportException("Cannot read int16", e);
+			throw new TransportException("Cannot read int32", e);
+		}
+	}
+
+	public long readInt64() throws TransportException {
+		try {
+			return is.readLong();
+		} catch (EOFException e) {
+			throw new ClosedConnectionException(e);
+		} catch (IOException e) {
+			throw new TransportException("Cannot read int32", e);
 		}
 	}
 
@@ -98,21 +109,35 @@
 	/**
 	 * Read 32-bit string length and then string themself by it length
 	 * Use this method only when sure no character accept ASCII will be read.
-	 * Use readBytes and character encoding conversion instead.
+	 * Use readBytes and character encoding conversion instead or {@link #readUtf8String} method
+	 * when utf-8 encoding needed.
 	 *
 	 * @return String read
 	 * @throws TransportException
 	 */
 	public String readString() throws TransportException {
-		// unset most sighificant (sign) bit 'cause InputStream#readFully
-		// reads
-		// [int] length bytes from stream. Change when realy need read sthing more
-		// than
-		// 2147483647 bytes lenght
+		// unset most significant (sign) bit 'cause InputStream#readFully reads
+		// [int] length bytes from stream. Change when really need read string more
+		// than 2147483647 bytes length
 		int length = readInt32() & Integer.MAX_VALUE;
 		return readString(length);
 	}
 
+	/**
+	 * Read 32-bit string length and then string themself by it length
+	 * Assume UTF-8 character encoding used
+	 *
+	 * @return String read
+	 * @throws TransportException
+	 */
+	public String readUtf8String() throws TransportException {
+		// unset most significant (sign) bit 'cause InputStream#readFully  reads
+		// [int] length bytes from stream. Change when really need read string more
+		// than 2147483647 bytes length
+		int length = readInt32() & Integer.MAX_VALUE;
+		return new String(readBytes(length));
+	}
+
 	public byte[] readBytes(int length) throws TransportException {
 		byte b[] = new byte[length];
 		return readBytes(b, 0, length);
@@ -128,4 +153,14 @@
 			throw new TransportException("Cannot read " + length + " bytes array", e);
 		}
 	}
+
+	public void skip(int length) throws TransportException {
+		try {
+			is.skipBytes(length);
+		} catch (EOFException e) {
+			throw new ClosedConnectionException(e);
+		} catch (IOException e) {
+			throw new TransportException("Cannot skip " + length + " bytes", e);
+		}
+	}
 }
\ No newline at end of file
--- a/src/main/java/com/glavsoft/transport/Writer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/transport/Writer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -73,6 +73,14 @@
 		write(i);
 	}
 
+	public void writeInt64(long i) throws TransportException {
+		try {
+			os.writeLong(i);
+		} catch (IOException e) {
+			throw new TransportException("Cannot write long", e);
+		}
+	}
+
 	public void write(int i) throws TransportException {
 		try {
 			os.writeInt(i);
--- a/src/main/java/com/glavsoft/utils/Keymap.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/utils/Keymap.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/main/java/com/glavsoft/utils/Strings.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/main/java/com/glavsoft/utils/Strings.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/AbstractConnectionWorkerFactory.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,36 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public abstract class AbstractConnectionWorkerFactory {
+    public abstract NetworkConnectionWorker createNetworkConnectionWorker();
+
+    public abstract RfbConnectionWorker createRfbConnectionWorker();
+
+    public abstract void setPredefinedPassword(String predefinedPassword);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/CancelConnectionException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,37 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.exceptions.CommonException;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class CancelConnectionException extends CommonException {
+
+    public CancelConnectionException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/ConnectionErrorException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,37 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.exceptions.CommonException;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class ConnectionErrorException extends CommonException {
+
+    public ConnectionErrorException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/ConnectionPresenter.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,290 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.mvp.Presenter;
+import com.glavsoft.viewer.swing.ConnectionParams;
+import com.glavsoft.viewer.swing.WrongParameterException;
+import com.glavsoft.viewer.swing.gui.ConnectionView;
+import com.glavsoft.viewer.swing.gui.ConnectionsHistory;
+
+import java.net.Socket;
+import java.util.logging.Logger;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class ConnectionPresenter extends Presenter {
+    public static final String PROPERTY_HOST_NAME = "HostName";
+    public static final String PROPERTY_RFB_PORT_NUMBER = "PortNumber";
+    public static final String PROPERTY_USE_SSH = "UseSsh";
+    private static final String PROPERTY_SSH_USER_NAME = "SshUserName";
+    private static final String PROPERTY_SSH_HOST_NAME = "SshHostName";
+    private static final String PROPERTY_SSH_PORT_NUMBER = "SshPortNumber";
+    private static final String PROPERTY_STATUS_BAR_MESSAGE = "Message";
+    private static final String PROPERTY_CONNECTION_IN_PROGRESS = "ConnectionInProgress";
+    public static final String CONNECTION_PARAMS_MODEL = "ConnectionParamsModel";
+    public static final String CONNECTIONS_HISTORY_MODEL = "ConnectionsHistoryModel";
+    public static final String CONNECTION_VIEW = "ConnectionView";
+
+    private final boolean hasSshSupport;
+    private final boolean allowInteractive;
+    private ConnectionsHistory connectionsHistory;
+    private ProtocolSettings rfbSettings;
+    private UiSettings uiSettings;
+    private final Logger logger;
+    private RfbConnectionWorker rfbConnectionWorker;
+    private AbstractConnectionWorkerFactory connectionWorkerFactory;
+    private NetworkConnectionWorker networkConnectionWorker;
+    private boolean needReconnection = true;
+
+    public ConnectionPresenter(boolean hasSshSupport, boolean allowInteractive) {
+        this.hasSshSupport = hasSshSupport;
+        this.allowInteractive = allowInteractive;
+        logger = Logger.getLogger(getClass().getName());
+    }
+
+    public void startConnection(ProtocolSettings rfbSettings, UiSettings uiSettings, int paramSettingsMask)
+            throws IllegalStateException {
+        this.rfbSettings = rfbSettings;
+        this.uiSettings = uiSettings;
+        if ( ! isModelRegisteredByName(CONNECTION_PARAMS_MODEL)) {
+            throw new IllegalStateException("No Connection Params model added.");
+        }
+        connectionsHistory = new ConnectionsHistory();
+        addModel(CONNECTIONS_HISTORY_MODEL, connectionsHistory);
+        syncModels(paramSettingsMask);
+        if (allowInteractive) {
+            show();
+            populate();
+        } else {
+            connect();
+        }
+    }
+
+    public void setUseSsh(boolean useSsh) {
+        setModelProperty(PROPERTY_USE_SSH, useSsh, boolean.class);
+    }
+
+    public void submitConnection(String hostName) throws WrongParameterException {
+        if (Strings.isTrimmedEmpty(hostName)) {
+            throw new WrongParameterException("Host name is empty", PROPERTY_HOST_NAME);
+        }
+        setModelProperty(PROPERTY_HOST_NAME, hostName);
+
+        final String rfbPort = (String) getViewPropertyOrNull(PROPERTY_RFB_PORT_NUMBER);
+        setModelProperty(PROPERTY_RFB_PORT_NUMBER, rfbPort);
+        try {
+            throwPossiblyHappenedException();
+        } catch (Throwable e) {
+            throw new WrongParameterException("Wrong Port", PROPERTY_HOST_NAME);
+        }
+        setSshOptions();
+
+        saveHistory();
+        populateFrom(CONNECTIONS_HISTORY_MODEL);
+
+        connect();
+    }
+
+    public void saveHistory() {
+        final ConnectionParams cp = (ConnectionParams) getModel(CONNECTION_PARAMS_MODEL);
+        connectionsHistory.reorder(cp, rfbSettings, uiSettings);
+        connectionsHistory.save();
+    }
+
+    private void connect() {
+        final ConnectionParams connectionParams = (ConnectionParams) getModel(CONNECTION_PARAMS_MODEL);
+        // TODO check connectionWorkerFactory is init
+        networkConnectionWorker = connectionWorkerFactory.createNetworkConnectionWorker();
+        networkConnectionWorker.setConnectionParams(connectionParams);
+        networkConnectionWorker.setPresenter(this);
+        networkConnectionWorker.setHasSshSupport(hasSshSupport);
+        networkConnectionWorker.execute();
+    }
+
+    public void connectionFailed() {
+        cancelConnection();
+        if (allowInteractive) {
+            enableConnectionDialog();
+        } else {
+            connect();
+        }
+    }
+
+    public void connectionCancelled() {
+        cancelConnection();
+        if (allowInteractive) {
+            enableConnectionDialog();
+        } else {
+            final ConnectionView connectionView = (ConnectionView) getView(CONNECTION_VIEW);
+            if (connectionView != null) {
+                connectionView.closeApp();
+            }
+        }
+    }
+
+    private void enableConnectionDialog() {
+        setViewProperty(PROPERTY_CONNECTION_IN_PROGRESS, false, boolean.class);
+    }
+
+    public void successfulNetworkConnection(Socket workingSocket) { // EDT
+        logger.info("Connected");
+        showMessage("Connected");
+        rfbConnectionWorker = connectionWorkerFactory.createRfbConnectionWorker();
+        rfbConnectionWorker.setWorkingSocket(workingSocket);
+        rfbConnectionWorker.setRfbSettings(rfbSettings);
+        rfbConnectionWorker.setUiSettings(uiSettings);
+        rfbConnectionWorker.setConnectionString(
+                getModelProperty(PROPERTY_HOST_NAME) + ":" + getModelProperty(PROPERTY_RFB_PORT_NUMBER));
+        rfbConnectionWorker.execute();
+    }
+
+    public void successfulRfbConnection() {
+        enableConnectionDialog();
+        getView(CONNECTION_VIEW).closeView();
+    }
+
+    public void cancelConnection() {
+        if (networkConnectionWorker != null) {
+            networkConnectionWorker.cancel();
+        }
+        if (rfbConnectionWorker != null) {
+            rfbConnectionWorker.cancel();
+        }
+    }
+
+    public void showConnectionErrorDialog(String message) {
+        final ConnectionView connectionView = (ConnectionView) getView(CONNECTION_VIEW);
+        if (connectionView != null) {
+            connectionView.showConnectionErrorDialog(message);
+        }
+    }
+
+    public void showReconnectDialog(String errorTitle, String errorMessage) {
+        final ConnectionView connectionView = (ConnectionView) getView(CONNECTION_VIEW);
+        if (connectionView != null) {
+            connectionView.showReconnectDialog(errorTitle, errorMessage);
+        }
+    }
+
+    private void setSshOptions() {
+		if (hasSshSupport) {
+            try {
+                final boolean useSsh = (Boolean)getViewProperty(PROPERTY_USE_SSH);
+                setModelProperty(PROPERTY_USE_SSH, useSsh, boolean.class);
+            } catch (PropertyNotFoundException e) {
+                //nop
+            }
+            setModelProperty(PROPERTY_SSH_USER_NAME, getViewPropertyOrNull(PROPERTY_SSH_USER_NAME));
+            setModelProperty(PROPERTY_SSH_HOST_NAME, getViewPropertyOrNull(PROPERTY_SSH_HOST_NAME));
+            setModelProperty(PROPERTY_SSH_PORT_NUMBER, getViewPropertyOrNull(PROPERTY_SSH_PORT_NUMBER));
+            setViewProperty(PROPERTY_SSH_PORT_NUMBER, getModelProperty(PROPERTY_SSH_PORT_NUMBER));
+        }
+	}
+
+    private void syncModels(int paramSettingsMask) {
+        final ConnectionParams cp = (ConnectionParams) getModel(CONNECTION_PARAMS_MODEL);
+        final ConnectionParams mostSuitableConnection = connectionsHistory.getMostSuitableConnection(cp);
+        cp.completeEmptyFieldsFrom(mostSuitableConnection);
+        rfbSettings.copyDataFrom(connectionsHistory.getProtocolSettings(mostSuitableConnection), paramSettingsMask & 0xffff);
+        uiSettings.copyDataFrom(connectionsHistory.getUiSettingsData(mostSuitableConnection), (paramSettingsMask >> 16) & 0xffff);
+        if ( ! cp.isHostNameEmpty()) {
+            connectionsHistory.reorder(cp, rfbSettings, uiSettings);
+        }
+
+//        protocolSettings.addListener(connectionsHistory);
+//        uiSettings.addListener(connectionsHistory);
+    }
+
+    public void populateFromHistoryItem(ConnectionParams connectionParams) {
+        setModelProperty(PROPERTY_HOST_NAME, connectionParams.hostName);
+        setModelProperty(PROPERTY_RFB_PORT_NUMBER, connectionParams.getPortNumber(), int.class);
+        setModelProperty(PROPERTY_USE_SSH, connectionParams.useSsh(), boolean.class);
+        setModelProperty(PROPERTY_SSH_HOST_NAME, connectionParams.sshHostName);
+        setModelProperty(PROPERTY_SSH_PORT_NUMBER, connectionParams.getSshPortNumber(), int.class);
+        setModelProperty(PROPERTY_SSH_USER_NAME, connectionParams.sshUserName);
+        populateFrom(CONNECTION_PARAMS_MODEL);
+        rfbSettings.copyDataFrom(connectionsHistory.getProtocolSettings(connectionParams));
+        uiSettings.copyDataFrom(connectionsHistory.getUiSettingsData(connectionParams));
+    }
+
+    public void clearHistory() {
+        connectionsHistory.clear();
+        connectionsHistory.reorder((ConnectionParams) getModel(CONNECTION_PARAMS_MODEL), rfbSettings, uiSettings);
+        populateFrom(CONNECTIONS_HISTORY_MODEL);
+        clearMessage();
+    }
+
+    public void showMessage(String message) {
+        setViewProperty(PROPERTY_STATUS_BAR_MESSAGE, message);
+    }
+
+    public void clearMessage() {
+        showMessage("");
+    }
+
+    public void setConnectionWorkerFactory(AbstractConnectionWorkerFactory connectionWorkerFactory) {
+        this.connectionWorkerFactory = connectionWorkerFactory;
+    }
+
+    public void reconnect(String predefinedPassword) {
+        connectionWorkerFactory.setPredefinedPassword(predefinedPassword);
+        if (allowInteractive) {
+            clearMessage();
+            enableConnectionDialog();
+            show();
+            populate();
+        } else if (needReconnection) {
+            connect();
+        }
+    }
+
+    public void clearPredefinedPassword() {
+        connectionWorkerFactory.setPredefinedPassword(null);
+    }
+
+    public UiSettings getUiSettings() {
+        return uiSettings;
+    }
+
+    public ProtocolSettings getRfbSettings() {
+        return rfbSettings;
+    }
+
+    public boolean needReconnection() {
+        return needReconnection;
+    }
+
+    public void setNeedReconnection(boolean need) {
+        needReconnection = need;
+    }
+
+    public boolean allowInteractive() {
+        return allowInteractive;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/ConnectionWorker.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,47 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface ConnectionWorker<T> {
+    /**
+     * The same as in {@link javax.swing.SwingWorker}
+     */
+    T doInBackground() throws Exception;
+
+    /**
+     * The same as in {@link javax.swing.SwingWorker}
+     */
+    void execute();
+
+    /**
+     * Should cancel worker.
+     *
+     * @return true if cancelled successful, false if not (ex. worker is already cancelled)
+     */
+    boolean cancel();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/NetworkConnectionWorker.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,41 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.viewer.swing.ConnectionParams;
+
+import java.net.Socket;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface NetworkConnectionWorker extends ConnectionWorker<Socket> {
+
+    void setConnectionParams(ConnectionParams connectionParams);
+
+    void setPresenter(ConnectionPresenter presenter);
+
+    void setHasSshSupport(boolean hasSshSupport);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/RfbConnectionWorker.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,46 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.rfb.protocol.Protocol;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+
+import javax.swing.*;
+import java.net.Socket;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface RfbConnectionWorker extends ConnectionWorker<Void> {
+
+    void setWorkingSocket(Socket workingSocket);
+
+    void setRfbSettings(ProtocolSettings rfbSettings);
+
+    void setUiSettings(UiSettings uiSettings);
+
+    void setConnectionString(String connectionString);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/UiSettings.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,190 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.core.SettingsChangedEvent;
+import com.glavsoft.rfb.IChangeSettingsListener;
+import com.glavsoft.viewer.swing.LocalMouseCursorShape;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class UiSettings {
+
+    public static final int MIN_SCALE_PERCENT = 10;
+	public static final int MAX_SCALE_PERCENT = 500;
+    private static final int SCALE_PERCENT_ZOOMING_STEP = 10;
+	
+	@SuppressWarnings("PointlessBitwiseExpression")
+    public static final int CHANGED_SCALE_FACTOR = 1 << 0;
+    public static final int CHANGED_MOUSE_CURSOR_SHAPE = 1 << 1;
+    public static final int CHANGED_FULL_SCREEN = 1 << 2;
+
+    private final List<IChangeSettingsListener> listeners = new LinkedList<IChangeSettingsListener>();
+    private int changedSettingsMask = 0;
+
+    private final UiSettingsData uiSettingsData;
+    public boolean showControls = true;
+
+    public UiSettings() {
+        uiSettingsData = new UiSettingsData();
+		changedSettingsMask = 0;
+	}
+
+	public UiSettings(UiSettings uiSettings) {
+        uiSettingsData = new UiSettingsData(
+                uiSettings.getScalePercent(), uiSettings.getMouseCursorShape(), uiSettings.isFullScreen());
+        this.changedSettingsMask = uiSettings.changedSettingsMask;
+    }
+
+	public double getScaleFactor() {
+		return uiSettingsData.getScalePercent() / 100.;
+	}
+
+	public void setScalePercent(double scalePercent) {
+        if (this.uiSettingsData.setScalePercent(scalePercent)) {
+		    changedSettingsMask |= CHANGED_SCALE_FACTOR;
+        }
+	}
+
+	public void addListener(IChangeSettingsListener listener) {
+		listeners.add(listener);
+	}
+
+	void fireListeners() {
+        if (null == listeners) return;
+		final SettingsChangedEvent event = new SettingsChangedEvent(new UiSettings(this));
+		changedSettingsMask = 0;
+		for (IChangeSettingsListener listener : listeners) {
+			listener.settingsChanged(event);
+		}
+	}
+
+    public void zoomOut() {
+	    double oldScaleFactor = uiSettingsData.getScalePercent();
+	    double scaleFactor = (int)(this.uiSettingsData.getScalePercent() / SCALE_PERCENT_ZOOMING_STEP) * SCALE_PERCENT_ZOOMING_STEP;
+	    if (scaleFactor == oldScaleFactor) {
+		    scaleFactor -= SCALE_PERCENT_ZOOMING_STEP;
+	    }
+	    if (scaleFactor < MIN_SCALE_PERCENT) {
+		    scaleFactor = MIN_SCALE_PERCENT;
+	    }
+	    setScalePercent(scaleFactor);
+	    fireListeners();
+    }
+
+    public void zoomIn() {
+	    double scaleFactor = (int)(this.uiSettingsData.getScalePercent() / SCALE_PERCENT_ZOOMING_STEP) * SCALE_PERCENT_ZOOMING_STEP + SCALE_PERCENT_ZOOMING_STEP;
+	    if (scaleFactor > MAX_SCALE_PERCENT) {
+		    scaleFactor = MAX_SCALE_PERCENT;
+	    }
+	    setScalePercent(scaleFactor);
+	    fireListeners();
+    }
+
+    public void zoomAsIs() {
+	    setScalePercent(100);
+	    fireListeners();
+    }
+
+	public void zoomToFit(int containerWidth, int containerHeight, int fbWidth, int fbHeight) {
+		int scalePromille = Math.min(1000 * containerWidth / fbWidth,
+				1000 * containerHeight / fbHeight);
+		while (fbWidth * scalePromille / 1000. > containerWidth ||
+				fbHeight * scalePromille / 1000. > containerHeight) {
+			scalePromille -= 1;
+		}
+		setScalePercent(scalePromille / 10.);
+		fireListeners();
+	}
+
+	public boolean isChangedMouseCursorShape() {
+		return (changedSettingsMask & CHANGED_MOUSE_CURSOR_SHAPE) == CHANGED_MOUSE_CURSOR_SHAPE;
+	}
+
+	public static boolean isUiSettingsChangedFired(SettingsChangedEvent event) {
+		return event.getSource() instanceof UiSettings;
+	}
+
+	public double getScalePercent() {
+		return uiSettingsData.getScalePercent();
+	}
+
+	public String getScalePercentFormatted() {
+		NumberFormat numberFormat = new DecimalFormat("###.#");
+		return numberFormat.format(uiSettingsData.getScalePercent());
+	}
+
+    public LocalMouseCursorShape getMouseCursorShape() {
+        return uiSettingsData.getMouseCursorShape();
+    }
+
+    public void setMouseCursorShape(LocalMouseCursorShape mouseCursorShape) {
+        if (this.uiSettingsData.setMouseCursorShape(mouseCursorShape)) {
+            changedSettingsMask |= CHANGED_MOUSE_CURSOR_SHAPE;
+            fireListeners();
+        }
+    }
+
+    public void copyDataFrom(UiSettingsData other) {
+        copyDataFrom(other, 0);
+    }
+    public void copyDataFrom(UiSettingsData other, int mask) {
+        if (null == other) return;
+        if ((mask & CHANGED_SCALE_FACTOR) == 0) uiSettingsData.setScalePercent(other.getScalePercent());
+        if ((mask & CHANGED_MOUSE_CURSOR_SHAPE) == 0) uiSettingsData.setMouseCursorShape(other.getMouseCursorShape());
+        if ((mask & CHANGED_FULL_SCREEN) == 0) uiSettingsData.setFullScreen(other.isFullScreen());
+    }
+
+    public void setFullScreen(boolean isFullScreen) {
+        if (uiSettingsData.setFullScreen(isFullScreen)) {
+            changedSettingsMask |= CHANGED_FULL_SCREEN;
+            fireListeners();
+        }
+    }
+
+    public boolean isFullScreen() {
+        return uiSettingsData.isFullScreen();
+    }
+
+    public UiSettingsData getData() {
+        return uiSettingsData;
+    }
+
+    @Override
+    public String toString() {
+        return "UiSettings{" +
+                "scalePercent=" + uiSettingsData.getScalePercent() +
+                ", fullScreen=" + uiSettingsData.isFullScreen() +
+                ", mouseCursorShape=" + uiSettingsData.getMouseCursorShape() +
+                '}';
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/UiSettingsData.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,102 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer;
+
+import com.glavsoft.viewer.swing.LocalMouseCursorShape;
+
+import java.io.Serializable;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class UiSettingsData implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private double scalePercent;
+    private LocalMouseCursorShape mouseCursorShape;
+    private boolean fullScreen;
+
+
+    public UiSettingsData() {
+        scalePercent = 100;
+        mouseCursorShape = LocalMouseCursorShape.DOT;
+        fullScreen = false;
+    }
+
+    public UiSettingsData(double scalePercent, LocalMouseCursorShape mouseCursorShape, boolean fullScreen) {
+        this.scalePercent = scalePercent;
+        this.mouseCursorShape = mouseCursorShape;
+        this.fullScreen = fullScreen;
+    }
+
+    public UiSettingsData(UiSettingsData other) {
+        this(other.getScalePercent(), other.getMouseCursorShape(), other.isFullScreen());
+    }
+
+    public double getScalePercent() {
+        return scalePercent;
+    }
+
+    public boolean setScalePercent(double scalePercent) {
+        if (this.scalePercent != scalePercent) {
+            this.scalePercent = scalePercent;
+            return true;
+        }
+        return false;
+    }
+
+
+    public LocalMouseCursorShape getMouseCursorShape() {
+        return mouseCursorShape;
+    }
+
+    public boolean setMouseCursorShape(LocalMouseCursorShape mouseCursorShape) {
+        if (this.mouseCursorShape != mouseCursorShape && mouseCursorShape != null) {
+            this.mouseCursorShape = mouseCursorShape;
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isFullScreen() {
+        return fullScreen;
+    }
+
+    public boolean setFullScreen(boolean fullScreen) {
+        if (this.fullScreen != fullScreen) {
+            this.fullScreen = fullScreen;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "UiSettingsData{" +
+                "scalePercent=" + scalePercent +
+                ", mouseCursorShape=" + mouseCursorShape +
+                ", fullScreen=" + fullScreen +
+                '}';
+    }
+}
\ No newline at end of file
--- a/src/viewer_swing/java/com/glavsoft/viewer/Viewer.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/Viewer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,105 +24,42 @@
 
 package com.glavsoft.viewer;
 
-import com.glavsoft.core.SettingsChangedEvent;
-import com.glavsoft.exceptions.*;
-import com.glavsoft.rfb.IChangeSettingsListener;
-import com.glavsoft.rfb.IPasswordRetriever;
-import com.glavsoft.rfb.IRfbSessionListener;
-import com.glavsoft.rfb.client.KeyEventMessage;
-import com.glavsoft.rfb.protocol.Protocol;
-import com.glavsoft.rfb.protocol.ProtocolContext;
 import com.glavsoft.rfb.protocol.ProtocolSettings;
-import com.glavsoft.transport.Reader;
-import com.glavsoft.transport.Writer;
-import com.glavsoft.utils.Keymap;
-import com.glavsoft.utils.Strings;
 import com.glavsoft.viewer.cli.Parser;
-import com.glavsoft.viewer.swing.*;
-import com.glavsoft.viewer.swing.gui.OptionsDialog;
-import com.glavsoft.viewer.swing.gui.PasswordDialog;
+import com.glavsoft.viewer.mvp.View;
+import com.glavsoft.viewer.swing.ConnectionParams;
+import com.glavsoft.viewer.swing.ParametersHandler;
+import com.glavsoft.viewer.swing.SwingConnectionWorkerFactory;
+import com.glavsoft.viewer.swing.SwingViewerWindowFactory;
+import com.glavsoft.viewer.swing.gui.ConnectionView;
 
 import javax.swing.*;
 import java.awt.*;
-import java.awt.Dialog.ModalityType;
-import java.awt.event.*;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.Socket;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
-import java.util.logging.Logger;
+import java.util.logging.*;
 
 @SuppressWarnings("serial")
-public class Viewer extends JApplet implements Runnable, IRfbSessionListener, WindowListener,
-		IChangeSettingsListener {
-	public static final int DEFAULT_PORT = 5900;
-
-	public static Logger logger = Logger.getLogger("com.glavsoft");
-	private static final Insets BUTTONS_MARGIN = new Insets(2, 2, 2, 2);
-	private boolean isZoomToFitSelected;
-	private boolean forceReconnection;
-	private String reconnectionReason;
-	private ContainerManager containerManager;
-
-	public Protocol getWorkingProtocol() {
-		return workingProtocol;
-	}
+public class Viewer extends JApplet implements Runnable, WindowListener {
 
-	public boolean isZoomToFitSelected() {
-		return isZoomToFitSelected;
-	}
-
-	public Surface getSurface() {
-		return surface;
-	}
-
-	public UiSettings getUiSettings() {
-		return uiSettings;
-	}
-
-	public void setZoomToFitSelected(boolean zoomToFitSelected) {
-		isZoomToFitSelected = zoomToFitSelected;
-	}
+	private Logger logger;
+    private int paramsMask;
+    private boolean allowAppletInteractiveConnections;
 
-	/**
-	 * Ask user for password if needed
-	 */
-	public class PasswordChooser implements IPasswordRetriever {
-		private final String passwordPredefined;
-		private final ParametersHandler.ConnectionParams connectionParams;
-		PasswordDialog passwordDialog;
-		private final JFrame owner;
-		private final WindowListener onClose;
-
-		public PasswordChooser(String passwordPredefined, ParametersHandler.ConnectionParams connectionParams,
-				JFrame owner, WindowListener onClose) {
-			this.passwordPredefined = passwordPredefined;
-			this.connectionParams = connectionParams;
-			this.owner = owner;
-			this.onClose = onClose;
-		}
+    private final ConnectionParams connectionParams;
+    private String passwordFromParams;
+    boolean isSeparateFrame = true;
+    boolean isApplet = true;
+    private final ProtocolSettings settings;
+    private final UiSettings uiSettings;
+    private volatile boolean isAppletStopped = false;
+    private ConnectionPresenter connectionPresenter;
 
-		@Override
-		public String getPassword() {
-			return Strings.isTrimmedEmpty(passwordPredefined) ?
-					getPasswordFromGUI() :
-					passwordPredefined;
-		}
-
-		private String getPasswordFromGUI() {
-			if (null == passwordDialog) {
-				passwordDialog = new PasswordDialog(owner, onClose, isApplet);
-			}
-			passwordDialog.setServerHostName(connectionParams.hostName);
-			passwordDialog.setVisible(true);
-			return passwordDialog.getPassword();
-		}
-	}
-
-	public static void main(String[] args) {
+    public static void main(String[] args) {
 		Parser parser = new Parser();
 		ParametersHandler.completeParserOptions(parser);
 
@@ -135,7 +72,7 @@
 		SwingUtilities.invokeLater(viewer);
 	}
 
-	public static void printUsage(String additional) {
+    public static void printUsage(String additional) {
 		System.out.println("Usage: java -jar (progfilename) [hostname [port_number]] [Options]\n" +
 				"    or\n"+
 				" java -jar (progfilename) [Options]\n" +
@@ -145,73 +82,52 @@
 				"Both option name and option value are case insensitive.");
 	}
 
-
-	private final ParametersHandler.ConnectionParams connectionParams;
-	protected String passwordFromParams;
-	protected Socket workingSocket;
-	protected Protocol workingProtocol;
-	protected JFrame containerFrame;
-	boolean isSeparateFrame = true;
-	protected boolean isApplet = true;
-	protected boolean showControls = true;
-	protected Surface surface;
-	protected final ProtocolSettings settings;
-	protected final UiSettings uiSettings;
-    protected boolean tryAgain;
-	private boolean isAppletStopped = false;
-	private volatile boolean isStoppingProcess;
-	private List<JComponent> kbdButtons;
-
 	public Viewer() {
-		connectionParams = new ParametersHandler.ConnectionParams();
+        logger = Logger.getLogger(getClass().getName());
+		connectionParams = new ConnectionParams();
 		settings = ProtocolSettings.getDefaultSettings();
 		uiSettings = new UiSettings();
 	}
 
 	private Viewer(Parser parser) {
 		this();
-		ParametersHandler.completeSettingsFromCLI(parser, connectionParams, settings, uiSettings);
-		showControls = ParametersHandler.showControls;
+        setLoggingLevel(parser.isSet(ParametersHandler.ARG_VERBOSE) ? Level.FINE :
+                parser.isSet(ParametersHandler.ARG_VERBOSE_MORE) ? Level.FINER :
+                        Level.INFO);
+
+        paramsMask = ParametersHandler.completeSettingsFromCLI(parser, connectionParams, settings, uiSettings);
 		passwordFromParams = parser.getValueFor(ParametersHandler.ARG_PASSWORD);
 		logger.info("TightVNC Viewer version " + ver());
 		isApplet = false;
 	}
 
+    private void setLoggingLevel(Level levelToSet) {
+        final Logger appLogger = Logger.getLogger("com.glavsoft");
+        appLogger.setLevel(levelToSet);
+        ConsoleHandler ch = null;
+        for (Handler h : appLogger.getHandlers()) {
+            if (h instanceof ConsoleHandler) {
+                ch = (ConsoleHandler) h;
+                break;
+            }
+        }
+        if (null == ch) {
+            ch = new ConsoleHandler();
+            appLogger.addHandler(ch);
+        }
+//        ch.setFormatter(new SimpleFormatter());
+        ch.setLevel(levelToSet);
+    }
 
-	@Override
-	public void rfbSessionStopped(final String reason) {
-		if (isStoppingProcess)
-			return;
-		cleanUpUISessionAndConnection();
-		SwingUtilities.invokeLater(new Runnable() {
-			@Override
-			public void run() {
-				forceReconnection = true;
-				reconnectionReason = reason;
-			}
-		});
-		// start new session
-		SwingUtilities.invokeLater(this);
-	}
 
-	private synchronized void cleanUpUISessionAndConnection() {
-		isStoppingProcess = true;
-		if (workingSocket != null && workingSocket.isConnected()) {
-			try {
-				workingSocket.close();
-			} catch (IOException e) { /*nop*/ }
-		}
-		if (containerFrame != null) {
-			containerFrame.dispose();
-			containerFrame = null;
-		}
-		isStoppingProcess = false;
-	}
-
-	@Override
+    @Override
 	public void windowClosing(WindowEvent e) {
 		if (e != null && e.getComponent() != null) {
-			e.getWindow().setVisible(false);
+            final Window w = e.getWindow();
+            if (w != null) {
+                w.setVisible(false);
+                w.dispose();
+            }
 		}
 		closeApp();
 	}
@@ -219,16 +135,18 @@
 	/**
 	 * Closes App(lication) or stops App(let).
 	 */
-	protected void closeApp() {
-		if (workingProtocol != null) {
-			workingProtocol.cleanUpSession();
-		}
-		cleanUpUISessionAndConnection();
-		tryAgain = false;
-		if (isApplet) {
-			logger.severe("Applet is stopped.");
-			isAppletStopped  = true;
-			repaint();
+    public void closeApp() {
+        if (connectionPresenter != null) {
+            connectionPresenter.cancelConnection();
+            logger.info("Connections cancelled.");
+        }
+        if (isApplet) {
+            if ( ! isAppletStopped) {
+                logger.severe("Applet is stopped.");
+                isAppletStopped  = true;
+                repaint();
+                stop();
+            }
 		} else {
 			System.exit(0);
 		}
@@ -253,321 +171,64 @@
 
 	@Override
 	public void init() {
-		ParametersHandler.completeSettingsFromApplet(this, connectionParams, settings, uiSettings);
-		showControls = ParametersHandler.showControls;
+		paramsMask = ParametersHandler.completeSettingsFromApplet(this, connectionParams, settings, uiSettings);
 		isSeparateFrame = ParametersHandler.isSeparateFrame;
 		passwordFromParams = getParameter(ParametersHandler.ARG_PASSWORD);
 		isApplet = true;
-
+        allowAppletInteractiveConnections = ParametersHandler.allowAppletInteractiveConnections;
 		repaint();
-		SwingUtilities.invokeLater(this);
-	}
+
+        try {
+            SwingUtilities.invokeAndWait(this);
+        } catch (Exception e) {
+            logger.severe(e.getMessage());
+        }
+    }
 
 	@Override
 	public void start() {
-		setSurfaceToHandleKbdFocus();
 		super.start();
 	}
 
-	@Override
-	public void run() {
-		ConnectionManager connectionManager = new ConnectionManager(this, isApplet);
-
-		if (forceReconnection) {
-			connectionManager.showReconnectDialog("Connection lost", reconnectionReason);
-			forceReconnection = false;
-		}
-		tryAgain = true;
-		while (tryAgain) {
-			workingSocket = connectionManager.connectToHost(connectionParams, settings);
-			if (null == workingSocket) {
-				closeApp();
-				break;
-			}
-			logger.info("Connected");
-
-			try {
-				workingSocket.setTcpNoDelay(true); // disable Nagle algorithm
-				Reader reader = new Reader(workingSocket.getInputStream());
-				Writer writer = new Writer(workingSocket.getOutputStream());
-
-				workingProtocol = new Protocol(reader, writer,
-						new PasswordChooser(passwordFromParams, connectionParams, containerFrame, this),
-						settings);
-				workingProtocol.handshake();
-
-                ClipboardControllerImpl clipboardController =
-		                new ClipboardControllerImpl(workingProtocol, settings.getRemoteCharsetName());
-				clipboardController.setEnabled(settings.isAllowClipboardTransfer());
-				settings.addListener(clipboardController);
-
-				surface = new Surface(workingProtocol, this, uiSettings.getScaleFactor());
-				settings.addListener(this);
-				uiSettings.addListener(surface);
-				containerFrame = createContainer();
-				connectionManager.setContainerFrame(containerFrame);
-				updateFrameTitle();
-
-				workingProtocol.startNormalHandling(this, surface, clipboardController);
-				tryAgain = false;
-			} catch (UnsupportedProtocolVersionException e) {
-				connectionManager.showReconnectDialog("Unsupported Protocol Version", e.getMessage());
-				logger.severe(e.getMessage());
-			} catch (UnsupportedSecurityTypeException e) {
-				connectionManager.showReconnectDialog("Unsupported Security Type", e.getMessage());
-				logger.severe(e.getMessage());
-			} catch (AuthenticationFailedException e) {
-				passwordFromParams = null;
-				connectionManager.showReconnectDialog("Authentication Failed", e.getMessage());
-				logger.severe(e.getMessage());
-			} catch (TransportException e) {
-				connectionManager.showReconnectDialog("Connection Error", "Connection Error" + ": " + e.getMessage());
-				logger.severe(e.getMessage());
-			} catch (IOException e) {
-				connectionManager.showReconnectDialog("Connection Error", "Connection Error" + ": " + e.getMessage());
-				logger.severe(e.getMessage());
-			} catch (FatalException e) {
-				connectionManager.showReconnectDialog("Connection Error", "Connection Error" + ": " + e.getMessage());
-				logger.severe(e.getMessage());
-			}
-		}
-	}
-
-	protected JFrame createContainer() {
-		containerManager = new ContainerManager(this);
-		Container container = containerManager.createContainer(surface, isSeparateFrame, isApplet);
+    private boolean checkJsch() {
+        try {
+            Class.forName("com.jcraft.jsch.JSch");
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
 
-		if (showControls) {
-			createButtonsPanel(container, workingProtocol, containerManager);
-			containerManager.registerResizeListener(container);
-			containerManager.updateZoomButtonsState();
-		}
-		setSurfaceToHandleKbdFocus();
-		return isSeparateFrame ? (JFrame)container : null;
-	}
-
-	public void packContainer() {
-		containerManager.pack();
-	}
-
-	protected void createButtonsPanel(final Container container, final ProtocolContext context, ContainerManager containerManager) {
-		JPanel buttonBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 1));
-
-		JButton optionsButton = new JButton(Utils.getButtonIcon("options"));
-		optionsButton.setToolTipText("Set Options");
-		optionsButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(optionsButton);
-		optionsButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				showOptionsDialog();
-				setSurfaceToHandleKbdFocus();
-			}
-		});
-
-		JButton infoButton = new JButton(Utils.getButtonIcon("info"));
-		infoButton.setToolTipText("Show connection info");
-		infoButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(infoButton);
-		infoButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				showConnectionInfoMessage(context.getRemoteDesktopName());
-				setSurfaceToHandleKbdFocus();
-			}
-		});
-
-		buttonBar.add(Box.createHorizontalStrut(10));
-		JButton refreshButton = new JButton(Utils.getButtonIcon("refresh"));
-		refreshButton.setToolTipText("Refresh screen");
-		refreshButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(refreshButton);
-		refreshButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				context.sendRefreshMessage();
-				setSurfaceToHandleKbdFocus();
-			}
-		});
-
-		containerManager.addZoomButtons(buttonBar, BUTTONS_MARGIN);
-
-		kbdButtons = new LinkedList<JComponent>();
-		buttonBar.add(Box.createHorizontalStrut(10));
-		JButton ctrlAltDelButton = new JButton(Utils.getButtonIcon("ctrl-alt-del"));
-		ctrlAltDelButton.setToolTipText("Send 'Ctrl-Alt-Del'");
-		ctrlAltDelButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(ctrlAltDelButton);
-		kbdButtons.add(ctrlAltDelButton);
-		ctrlAltDelButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				sendCtrlAltDel(context);
-				setSurfaceToHandleKbdFocus();
-			}
-		});
+    @Override
+	public void run() {
 
-		JButton winButton = new JButton(Utils.getButtonIcon("win"));
-		winButton.setToolTipText("Send 'Win' key as 'Ctrl-Esc'");
-		winButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(winButton);
-		kbdButtons.add(winButton);
-		winButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				sendWinKey(context);
-				setSurfaceToHandleKbdFocus();
-			}
-		});
-
-		JToggleButton ctrlButton = new JToggleButton(Utils.getButtonIcon("ctrl"));
-		ctrlButton.setToolTipText("Ctrl Lock");
-		ctrlButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(ctrlButton);
-		kbdButtons.add(ctrlButton);
-		ctrlButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				setSurfaceToHandleKbdFocus();
-			}
-		});
-		ctrlButton.addItemListener(new ItemListener() {
-			@Override
-			public void itemStateChanged(ItemEvent e) {
-				if (e.getStateChange() == ItemEvent.SELECTED) {
-					context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true));
-				} else {
-					context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false));
-				}
-			}
-		});
-
-		JToggleButton altButton = new JToggleButton(Utils.getButtonIcon("alt"));
-		kbdButtons.add(altButton);
-		altButton.setToolTipText("Alt Lock");
-		altButton.setMargin(BUTTONS_MARGIN);
-		buttonBar.add(altButton);
-		kbdButtons.add(altButton);
-		altButton.addItemListener(new ItemListener() {
-			@Override
-			public void itemStateChanged(ItemEvent e) {
-				if (e.getStateChange() == ItemEvent.SELECTED) {
-					context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, true));
-				} else {
-					context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, false));
-				}
-			}
-		});
-		altButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				setSurfaceToHandleKbdFocus();
-			}
-		});
-		ModifierButtonEventListener modifierButtonListener = new ModifierButtonEventListener();
-		modifierButtonListener.addButton(KeyEvent.VK_CONTROL, ctrlButton);
-		modifierButtonListener.addButton(KeyEvent.VK_ALT, altButton);
-		surface.addModifierListener(modifierButtonListener);
-
-//		JButton fileTransferButton = new JButton(Utils.getButtonIcon("file-transfer"));
-//		fileTransferButton.setMargin(buttonsMargin);
-//		buttonBar.add(fileTransferButton);
-
-		buttonBar.add(Box.createHorizontalStrut(10));
-
-		JButton closeButton = new JButton(Utils.getButtonIcon("close"));
-		closeButton.setToolTipText(isApplet ? "Disconnect" : "Close");
-		closeButton.setMargin(BUTTONS_MARGIN);
-		closeButton.setAlignmentX(RIGHT_ALIGNMENT);
-		buttonBar.add(closeButton);
-		closeButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				closeApp();
-			}
-		});
+        final boolean hasJsch = checkJsch();
+        final boolean allowInteractive = allowAppletInteractiveConnections || ! isApplet;
+        connectionPresenter = new ConnectionPresenter(hasJsch, allowInteractive);
+        connectionPresenter.addModel("ConnectionParamsModel", connectionParams);
+        final ConnectionView connectionView = new ConnectionView(
+                Viewer.this, // appWindowListener
+                connectionPresenter, hasJsch);
+        connectionPresenter.addView(ConnectionPresenter.CONNECTION_VIEW, connectionView);
+        if (isApplet) {
+            connectionPresenter.addView("AppletStatusStringView", new View() {
+                @Override
+                public void showView() { /*nop*/ }
+                @Override
+                public void closeView() { /*nop*/ }
+                @SuppressWarnings("UnusedDeclaration")
+                public void setMessage(String message) {
+                    Viewer.this.getAppletContext().showStatus(message);
+                }
+            });
+        }
 
-		container.add(buttonBar, BorderLayout.NORTH);
-	}
-
-	protected void updateFrameTitle() {
-		if (containerFrame != null) {
-			containerFrame.setTitle(
-					workingProtocol.getRemoteDesktopName() + " [zoom: " + uiSettings.getScalePercentFormatted() + "%]");
-		}
-	}
-
-	protected void setSurfaceToHandleKbdFocus() {
-		if (surface != null && ! surface.requestFocusInWindow()) {
-			surface.requestFocus();
-		}
-	}
-
-	@Override
-	public void settingsChanged(SettingsChangedEvent e) {
-		ProtocolSettings settings = (ProtocolSettings) e.getSource();
-		setEnabledKbdButtons( ! settings.isViewOnly());
-	}
-
-	private void setEnabledKbdButtons(boolean enabled) {
-		if (kbdButtons != null) {
-			for (JComponent b : kbdButtons) {
-				b.setEnabled(enabled);
-			}
-		}
-	}
-
-	private void showOptionsDialog() {
-		OptionsDialog optionsDialog = new OptionsDialog(containerFrame);
-		optionsDialog.initControlsFromSettings(settings, false);
-		optionsDialog.setVisible(true);
-	}
+        SwingViewerWindowFactory viewerWindowFactory = new SwingViewerWindowFactory(isSeparateFrame, isApplet, this);
 
-	private void showConnectionInfoMessage(final String title) {
-		StringBuilder message = new StringBuilder();
-		message.append("Connected to: ").append(title).append("\n");
-		message.append("Host: ").append(connectionParams.hostName)
-			.append(" Port: ").append(connectionParams.portNumber).append("\n\n");
-
-		message.append("Desktop geometry: ")
-			.append(String.valueOf(surface.getWidth()))
-			.append(" \u00D7 ") // multiplication sign
-			.append(String.valueOf(surface.getHeight())).append("\n");
-		message.append("Color format: ")
-			.append(String.valueOf(Math.round(Math.pow(2, workingProtocol.getPixelFormat().depth))))
-			.append(" colors (")
-			.append(String.valueOf(workingProtocol.getPixelFormat().depth))
-			.append(" bits)\n");
-		message.append("Current protocol version: ")
-			.append(settings.getProtocolVersion());
-		if (settings.isTight()) {
-			message.append("tight");
-		}
-		message.append("\n");
+        connectionPresenter.setConnectionWorkerFactory(
+                new SwingConnectionWorkerFactory(connectionView.getFrame(), passwordFromParams, connectionPresenter, viewerWindowFactory));
 
-		JOptionPane infoPane = new JOptionPane(message.toString(), JOptionPane.INFORMATION_MESSAGE);
-		final JDialog infoDialog = infoPane.createDialog(containerFrame, "VNC connection info");
-		infoDialog.setModalityType(ModalityType.MODELESS);
-		try {
-			infoDialog.setAlwaysOnTop(true);
-		} catch (SecurityException e) { /*nop*/ }
-		infoDialog.setVisible(true);
-	}
-
-	private void sendCtrlAltDel(ProtocolContext context) {
-		context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true));
-		context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, true));
-		context.sendMessage(new KeyEventMessage(Keymap.K_DELETE, true));
-		context.sendMessage(new KeyEventMessage(Keymap.K_DELETE, false));
-		context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, false));
-		context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false));
-	}
-
-	private void sendWinKey(ProtocolContext context) {
-		context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true));
-		context.sendMessage(new KeyEventMessage(Keymap.K_ESCAPE, true));
-		context.sendMessage(new KeyEventMessage(Keymap.K_ESCAPE, false));
-		context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false));
+        connectionPresenter.startConnection(settings, uiSettings, paramsMask);
 	}
 
 	@Override
@@ -583,7 +244,7 @@
 	@Override
 	public void windowDeactivated(WindowEvent e) { /* nop */ }
 
-	protected static String ver() {
+	public static String ver() {
 		final InputStream mfStream = Viewer.class.getClassLoader().getResourceAsStream(
 				"META-INF/MANIFEST.MF");
 		if (null == mfStream) {
--- a/src/viewer_swing/java/com/glavsoft/viewer/cli/Parser.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/cli/Parser.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,13 +24,13 @@
 
 package com.glavsoft.viewer.cli;
 
+import com.glavsoft.utils.Strings;
+
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import com.glavsoft.utils.Strings;
-
 /**
  * Command line interface parameters parser
  */
@@ -70,7 +70,7 @@
 
 	public boolean isSet(String param) {
 		Option op = options.get(param.toLowerCase());
-		return op != null ? op.isSet : false;
+		return op != null && op.isSet;
 	}
 
 	public boolean isSetPlainOptions() {
@@ -89,7 +89,6 @@
 	 * Command line interface option
 	 */
 	private static class Option {
-		@SuppressWarnings("unused")
 		protected String opName, defaultValue, desc, value;
 		protected boolean isSet = false;
 		public Option(String opName, String defaultValue, String desc) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/mvp/Model.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,33 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.mvp;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface Model {
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/mvp/Presenter.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,259 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.mvp;
+
+import com.glavsoft.exceptions.CommonException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class Presenter {
+    private final Map<String, View> registeredViews;
+    private final Map<String, Model> registeredModels;
+    static private Logger logger = Logger.getLogger(Presenter.class.getName());
+    private Throwable savedInvocationTargetException;
+
+    public Presenter() {
+        registeredViews = new HashMap<String, View>();
+        registeredModels = new HashMap<String, Model>();
+    }
+
+    public void addView(String name, View view) {
+        registeredViews.put(name, view);
+    }
+
+    public void  addModel(String name, Model model) {
+        registeredModels.put(name, model);
+    }
+
+    protected void populate() {
+        savedInvocationTargetException = null;
+        for (Map.Entry<String, Model> entry : registeredModels.entrySet()) {
+            String modelName = entry.getKey();
+            Model model = entry.getValue();
+            populateFrom(modelName, model);
+        }
+    }
+
+    public void  populateFrom(String modelName) {
+        Model model = registeredModels.get(modelName);
+        if (modelName != null) {
+            populateFrom(modelName, model);
+        } else {
+            logger.finer("Cannot find model: " + modelName);
+        }
+    }
+
+    private void populateFrom(String modelName, Model model) {
+        Method methods[] = model.getClass().getDeclaredMethods();
+        for (Method m : methods) {
+            if (m.getName().startsWith("get") && m.getParameterTypes().length == 0) {
+                String propertyName = m.getName().substring(3);
+                try {
+                    final Object property = m.invoke(model);
+                    logger.finest("Load: " + modelName + ".get" + propertyName + "() # => " + property +
+                        "  type: " + m.getReturnType());
+                    setViewProperty(propertyName, property, m.getReturnType()); // TODO this can set savedInvocationTargetEx, so what to do whith it?
+                } catch (IllegalAccessException e) {
+                    // nop
+                } catch (InvocationTargetException e) {
+                    savedInvocationTargetException = e.getCause(); // TODO may be skip it?
+                    break;
+                }
+            }
+        }
+    }
+
+    protected boolean isModelRegisteredByName(String modelName) {
+        return registeredModels.containsKey(modelName);
+    }
+
+    protected Model getModel(String modelName) {
+        return registeredModels.get(modelName);
+    }
+
+    protected void show() {
+        for (View v : registeredViews.values()) {
+            v.showView();
+        }
+    }
+
+
+    protected void save() {
+        savedInvocationTargetException = null;
+        for (Map.Entry<String, Model> entry : registeredModels.entrySet()) {
+            String modelName = entry.getKey();
+            Model model = entry.getValue();
+            Method methods[] = model.getClass().getDeclaredMethods();
+            for (Method m : methods) {
+                if (m.getName().startsWith("set")) {
+                    String propertyName = m.getName().substring(3);
+                    try {
+                        final Object viewProperty = getViewProperty(propertyName);
+                        m.invoke(model, viewProperty);
+                        logger.finest("Save: " + modelName + ".set" + propertyName + "( " + viewProperty + " )");
+                    } catch (IllegalAccessException e) {
+                        // nop
+                    } catch (InvocationTargetException e) {
+                        savedInvocationTargetException = e.getCause();
+                        break;
+                    } catch (PropertyNotFoundException e) {
+                        // nop
+                    }
+                }
+            }
+        }
+    }
+
+    public Object getViewPropertyOrNull(String propertyName) {
+        try {
+            return getViewProperty(propertyName);
+        } catch (PropertyNotFoundException e) {
+            return null;
+        }
+    }
+
+    public Object getViewProperty(String propertyName) throws PropertyNotFoundException {
+        savedInvocationTargetException = null;
+        logger.finest("get" + propertyName + "()");
+        for (Map.Entry<String, View> entry : registeredViews.entrySet()) {
+            String viewName = entry.getKey();
+            View view = entry.getValue();
+            try {
+                Method getter = view.getClass().getMethod("get" + propertyName, new Class[0]);
+                final Object res = getter.invoke(view);
+                logger.finest("----from view: " + viewName + ".get" + propertyName + "() # +> " + res);
+                return res;
+                // oops, only first getter will be found TODO?
+            } catch (NoSuchMethodException e) {
+                // nop
+            } catch (InvocationTargetException e) {
+                savedInvocationTargetException = e.getCause();
+                break;
+            } catch (IllegalAccessException e) {
+                // nop
+            }
+        }
+        throw new PropertyNotFoundException(propertyName);
+    }
+
+    public Object getModelProperty(String propertyName) {
+        savedInvocationTargetException = null;
+        logger.finest("get" + propertyName + "()");
+        for (String modelName : registeredModels.keySet()) {
+            Model model = registeredModels.get(modelName);
+            try {
+                Method getter = model.getClass().getMethod("get" + propertyName, new Class[0]);
+                final Object res = getter.invoke(model);
+                logger.finest("----from model: " + modelName + ".get" + propertyName + "() # +> " + res);
+                return res;
+                // oops, only first getter will be found TODO?
+            } catch (NoSuchMethodException e) {
+                // nop
+            } catch (InvocationTargetException e) {
+                savedInvocationTargetException = e.getCause();
+                break;
+            } catch (IllegalAccessException e) {
+                // nop
+            }
+        }
+//        savedInvocationTargetException = new PropertyNotFoundException(propertyName);
+        return null;
+    }
+
+    public void setViewProperty(String propertyName, Object newValue) {
+        setViewProperty(propertyName, newValue, newValue.getClass());
+    }
+
+    public void setViewProperty(String propertyName, Object newValue, Class<?> valueType) {
+        savedInvocationTargetException = null;
+        logger.finest("set" + propertyName + "( " + newValue + " ) type: " + valueType);
+        for (Map.Entry<String, View> entry : registeredViews.entrySet()) {
+            String viewName = entry.getKey();
+            View view = entry.getValue();
+            try {
+                Method setter = view.getClass().getMethod("set" + propertyName, valueType);
+                setter.invoke(view, newValue);
+                logger.finest("----to view: " + viewName + ".set" + propertyName + "( " + newValue + " )");
+            } catch (NoSuchMethodException e) {
+                // nop
+            } catch (InvocationTargetException e) {
+                e.getCause().printStackTrace();
+                savedInvocationTargetException = e.getCause();
+                break;
+            } catch (IllegalAccessException e) {
+                // nop
+            }
+        }
+    }
+
+    protected void throwPossiblyHappenedException() throws Throwable {
+        if (savedInvocationTargetException != null) {
+            savedInvocationTargetException = null;
+            throw savedInvocationTargetException;
+        }
+    }
+
+    protected View getView(String name) {
+        return registeredViews.get(name);
+    }
+
+    protected class PropertyNotFoundException extends CommonException {
+        public PropertyNotFoundException(String message) {
+            super(message);
+        }
+    }
+
+    public void setModelProperty(String propertyName, Object newValue) {
+        setModelProperty(propertyName, newValue, newValue.getClass());
+    }
+
+    public void setModelProperty(String propertyName, Object newValue, Class<?> valueType) {
+        savedInvocationTargetException = null;
+        logger.finest("set" + propertyName + "( " + newValue + " )");
+        for (Map.Entry<String, Model> entry : registeredModels.entrySet()) {
+            String modelName = entry.getKey();
+            Model model = entry.getValue();
+            try {
+                Method method = model.getClass().getMethod("set" + propertyName, valueType);
+                method.invoke(model, newValue);
+                logger.finest("----for model: " + modelName);
+            } catch (NoSuchMethodException e) {
+                // nop
+            } catch (InvocationTargetException e) {
+                savedInvocationTargetException = e.getCause();
+                break;
+            } catch (IllegalAccessException e) {
+                // nop
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/mvp/View.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,33 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.mvp;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface View {
+    void showView();
+    void closeView();
+}
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/ClipboardControllerImpl.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ClipboardControllerImpl.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ConnectionParams.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,220 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.mvp.Model;
+
+/**
+* @author dime at tightvnc.com
+*/
+public class ConnectionParams implements Model {
+	public static final int DEFAULT_SSH_PORT = 22;
+	private static final int DEFAULT_RFB_PORT = 5900;
+
+	public String hostName;
+	private int portNumber;
+	public String sshUserName;
+	public String sshHostName;
+	private int sshPortNumber;
+
+	private boolean useSsh;
+
+	public ConnectionParams(String hostName, int portNumber, boolean useSsh, String sshHostName, int sshPortNumber, String sshUserName) {
+		this.hostName = hostName;
+		this.portNumber = portNumber;
+		this.sshUserName = sshUserName;
+		this.sshHostName = sshHostName;
+		this.sshPortNumber = sshPortNumber;
+		this.useSsh = useSsh;
+	}
+
+	public ConnectionParams(ConnectionParams cp) {
+		this.hostName = cp.hostName != null? cp.hostName: "";
+		this.portNumber = cp.portNumber;
+		this.sshUserName = cp.sshUserName;
+		this.sshHostName = cp.sshHostName;
+		this.sshPortNumber = cp.sshPortNumber;
+		this.useSsh = cp.useSsh;
+	}
+
+	public ConnectionParams() {
+		hostName = "";
+        sshUserName = "";
+        sshHostName = "";
+	}
+
+	public boolean isHostNameEmpty() {
+		return Strings.isTrimmedEmpty(hostName);
+	}
+
+	public void parseRfbPortNumber(String port) throws WrongParameterException {
+		try {
+			portNumber = Integer.parseInt(port);
+		} catch (NumberFormatException e) {
+            portNumber = 0;
+            if ( ! Strings.isTrimmedEmpty(port)) {
+                throw new WrongParameterException("Wrong port number: " + port + "\nMust be in 0..65535");
+            }
+        }
+        if (portNumber > 65535 || portNumber < 0) throw new WrongParameterException("Port number is out of range: " + port + "\nMust be in 0..65535");
+	}
+	public void parseSshPortNumber(String port) {
+		try {
+			sshPortNumber = Integer.parseInt(port);
+		} catch (NumberFormatException e) { /*nop*/ }
+	}
+
+    public void setHostName(String hostName) {
+        this.hostName = hostName;
+    }
+    public String getHostName() {
+        return this.hostName;
+    }
+
+    public void setPortNumber(String port) throws WrongParameterException {
+        this.parseRfbPortNumber(port);
+    }
+
+    public void setPortNumber(int port) {
+        this.portNumber = port;
+    }
+
+    public int getPortNumber() {
+        return 0 == portNumber ? DEFAULT_RFB_PORT : portNumber;
+    }
+
+    public void setSshPortNumber(String port) {
+        this.parseSshPortNumber(port);
+    }
+
+    public void setSshPortNumber(int port) {
+        this.sshPortNumber = port;
+    }
+
+    public int getSshPortNumber() {
+        return 0 == sshPortNumber ? DEFAULT_SSH_PORT: sshPortNumber;
+    }
+
+    public void setUseSsh(boolean useSsh) {
+        this.useSsh = useSsh;
+    }
+
+    public boolean useSsh() {
+        return useSsh && ! Strings.isTrimmedEmpty(sshHostName);
+    }
+
+    public boolean getUseSsh() {
+        return this.useSsh();
+    }
+
+    public String getSshUserName() {
+        return this.sshUserName;
+    }
+
+    public void setSshUserName(String sshUserName) {
+        this.sshUserName = sshUserName;
+    }
+
+    public String getSshHostName() {
+        return this.sshHostName;
+    }
+
+    public void setSshHostName(String sshHostName) {
+        this.sshHostName = sshHostName;
+    }
+
+    public void completeEmptyFieldsFrom(ConnectionParams from) {
+        if (null == from) return;
+		if (Strings.isTrimmedEmpty(hostName) && ! Strings.isTrimmedEmpty(from.hostName)) {
+			hostName = from.hostName;
+		}
+        if ( 0 == portNumber && from.portNumber != 0) {
+			portNumber = from.portNumber;
+		}
+		if (Strings.isTrimmedEmpty(sshUserName) && ! Strings.isTrimmedEmpty(from.sshUserName)) {
+			sshUserName = from.sshUserName;
+		}
+		if (Strings.isTrimmedEmpty(sshHostName) && ! Strings.isTrimmedEmpty(from.sshHostName)) {
+			sshHostName = from.sshHostName;
+		}
+		if ( 0 == sshPortNumber && from.sshPortNumber != 0) {
+			sshPortNumber = from.sshPortNumber;
+		}
+		useSsh |= from.useSsh;
+	}
+
+	@Override
+	public String toString() {
+		return hostName != null ? hostName : "";
+//        return (hostName != null ? hostName : "") + ":" + portNumber + " " + useSsh + " " + sshUserName + "@" + sshHostName + ":" + sshPortNumber;
+    }
+
+    public String toPrint() {
+        return "ConnectionParams{" +
+                "hostName='" + hostName + '\'' +
+                ", portNumber=" + portNumber +
+                ", sshUserName='" + sshUserName + '\'' +
+                ", sshHostName='" + sshHostName + '\'' +
+                ", sshPortNumber=" + sshPortNumber +
+                ", useSsh=" + useSsh +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (null == obj || ! (obj instanceof ConnectionParams)) return false;
+        if (this == obj) return true;
+        ConnectionParams o = (ConnectionParams) obj;
+        return isEqualsNullable(hostName, o.hostName) && getPortNumber() == o.getPortNumber() &&
+                useSsh == o.useSsh && isEqualsNullable(sshHostName, o.sshHostName) &&
+                getSshPortNumber() == o.getSshPortNumber() && isEqualsNullable(sshUserName, o.sshUserName);
+    }
+
+    private boolean isEqualsNullable(String one, String another) {
+        //noinspection StringEquality
+        return one == another || (null == one? "" : one).equals(null == another? "" : another);
+    }
+
+    @Override
+    public int hashCode() {
+        long hash = (hostName != null? hostName.hashCode() : 0) +
+                portNumber * 17 +
+                (useSsh ? 781 : 693) +
+                (sshHostName != null? sshHostName.hashCode() : 0) * 23 +
+                (sshUserName != null? sshUserName.hashCode() : 0) * 37 +
+                sshPortNumber * 41;
+        return (int)hash;
+    }
+
+    public void clearFields() {
+        hostName = "";
+        portNumber = 0;
+        useSsh = false;
+        sshHostName = null;
+        sshUserName = null;
+        sshPortNumber = 0;
+    }
+}
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/KeyEventListener.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/KeyEventListener.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -54,8 +54,8 @@
 		final int location = e.getKeyLocation();
 		if (0xffff == keyChar) { keyChar = convertToAscii? convertor.convert(keyChar, e) : 0; }
 		if (keyChar < 0x20) {
-			if (e.isControlDown()) {
-				keyChar += 0x60; // TODO: From legacy code. What's this?
+			if (e.isControlDown() && keyChar != e.getKeyCode()) {
+				keyChar += 0x60; // to differ Ctrl-H from Ctrl-Backspace
 			} else {
 				switch (keyChar) {
 				case KeyEvent.VK_BACK_SPACE: keyChar = K_BACK_SPACE; break;
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/KeyboardConvertor.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/KeyboardConvertor.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/LocalMouseCursorShape.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,21 @@
+package com.glavsoft.viewer.swing;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public enum LocalMouseCursorShape {
+    DOT("dot"),
+    SMALL_DOT("smalldot"),
+    SYSTEM_DEFAULT("default"),
+    NO_CURSOR("nocursor");
+
+    private String cursorName;
+
+    LocalMouseCursorShape(String name) {
+        this.cursorName = name;
+    }
+
+    public String getCursorName() {
+        return  cursorName;
+    }
+}
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/ModifierButtonEventListener.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ModifierButtonEventListener.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/MouseEventListener.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/MouseEventListener.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,17 +24,16 @@
 
 package com.glavsoft.viewer.swing;
 
+import com.glavsoft.rfb.IRepaintController;
+import com.glavsoft.rfb.client.PointerEventMessage;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+
+import javax.swing.event.MouseInputAdapter;
 import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseWheelEvent;
 import java.awt.event.MouseWheelListener;
 
-import javax.swing.event.MouseInputAdapter;
-
-import com.glavsoft.rfb.IRepaintController;
-import com.glavsoft.rfb.client.PointerEventMessage;
-import com.glavsoft.rfb.protocol.ProtocolContext;
-
 public class MouseEventListener extends MouseInputAdapter
 implements MouseWheelListener {
 	private static final byte BUTTON_LEFT = 1;
@@ -56,8 +55,6 @@
 
 	public void processMouseEvent(MouseEvent mouseEvent,
 			MouseWheelEvent mouseWheelEvent, boolean moved) {
-		// TODO handle scaling
-
 		byte buttonMask = 0;
 		if (null == mouseEvent && mouseWheelEvent != null) {
 			mouseEvent = mouseWheelEvent;
@@ -106,11 +103,6 @@
 	}
 
 	@Override
-	public void mouseEntered(MouseEvent mouseEvent) {
-		// TODO Auto-generated method stub
-	}
-
-	@Override
 	public void mouseDragged(MouseEvent mouseEvent) {
 		processMouseEvent(mouseEvent, null, true);
 	}
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/ParametersHandler.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ParametersHandler.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -28,7 +28,7 @@
 import com.glavsoft.rfb.protocol.LocalPointer;
 import com.glavsoft.rfb.protocol.ProtocolSettings;
 import com.glavsoft.utils.Strings;
-import com.glavsoft.viewer.Viewer;
+import com.glavsoft.viewer.UiSettings;
 import com.glavsoft.viewer.cli.Parser;
 
 import javax.swing.*;
@@ -36,6 +36,7 @@
 public class ParametersHandler {
 	public static final String ARG_LOCAL_POINTER = "LocalPointer";
 	public static final String ARG_SCALING_FACTOR = "ScalingFactor";
+    public static final String ARG_FULL_SCREEN = "FullScreen";
 	public static final String ARG_COLOR_DEPTH = "ColorDepth";
 	public static final String ARG_JPEG_IMAGE_QUALITY = "JpegImageQuality";
 	public static final String ARG_COMPRESSION_LEVEL = "CompressionLevel";
@@ -49,30 +50,23 @@
 	public static final String ARG_PORT = "port";
 	public static final String ARG_HOST = "host";
 	public static final String ARG_HELP = "help";
+    public static final String ARG_VERBOSE = "v";
+    public static final String ARG_VERBOSE_MORE = "vv";
 	public static final String ARG_CONVERT_TO_ASCII = "ConvertToASCII";
 	public static final String ARG_ALLOW_CLIPBOARD_TRANSFER = "AllowClipboardTransfer";
 	public static final String ARG_REMOTE_CHARSET = "RemoteCharset";
+	public static final String ARG_SSH_HOST = "sshHost";
+	public static final String ARG_SSH_USER = "sshUser";
+	public static final String ARG_SSH_PORT = "sshPort";
+    public static final String ARG_ALLOW_APPLET_INTERACTIVE_CONNECTIONS = "AllowAppletInteractiveConnections";
 
-	public static class ConnectionParams {
-		public String hostName;
-		public int portNumber = Viewer.DEFAULT_PORT;
-		public boolean isHostNameEmpty() {
-			return Strings.isTrimmedEmpty(hostName);
-		}
-		void parsePortNumber(String port) {
-			try {
-				portNumber = Integer.parseInt(port);
-			} catch (NumberFormatException e) { /*nop*/ }
-		}
-	}
+	public static boolean isSeparateFrame;
+    public static boolean allowAppletInteractiveConnections;
 
-	public static boolean showControls;
-	public static boolean isSeparateFrame;
-
-	public static void completeParserOptions(Parser parser) {
+    public static void completeParserOptions(Parser parser) {
 		parser.addOption(ARG_HELP, null, "Print this help.");
-		parser.addOption(ARG_HOST, null, "Server host name.");
-		parser.addOption(ARG_PORT, "5900", "Port number.");
+		parser.addOption(ARG_HOST, "", "Server host name.");
+		parser.addOption(ARG_PORT, "0", "Port number.");
 		parser.addOption(ARG_PASSWORD, null, "Password to the server.");
 		parser.addOption(ARG_SHOW_CONTROLS, null, "Set to \"No\" if you want to get rid of that " +
 				"button panel at the top. Default: \"Yes\".");
@@ -94,9 +88,12 @@
 				"\"Tight\" and \"Zlib\" encodings. Values: 1-9. Level 1 uses minimum of CPU " +
 				"time on the server but achieves weak compression ratios. Level 9 offers best " +
 				"compression but may be slow.");
-		parser.addOption(ARG_JPEG_IMAGE_QUALITY, null, "Use the specified image quality level " +
+        //noinspection ConstantConditions
+        parser.addOption(ARG_JPEG_IMAGE_QUALITY, null, "Use the specified image quality level " +
 				"in \"Tight\" encoding. Values: 1-9, Lossless. Default value: " +
-				(String.valueOf(ProtocolSettings.DEFAULT_JPEG_QUALITY))
+				(ProtocolSettings.DEFAULT_JPEG_QUALITY > 0 ?
+						String.valueOf(ProtocolSettings.DEFAULT_JPEG_QUALITY) :
+						"\"Lossless\"")
 				+ ". To prevent server of using " +
 				"lossy JPEG compression in \"Tight\" encoding, use \"Lossless\" value here.");
 		parser.addOption(ARG_LOCAL_POINTER, null, "Possible values: on/yes/true (draw pointer locally), off/no/false (let server draw pointer), hide). " +
@@ -107,17 +104,26 @@
 		parser.addOption(ARG_SCALING_FACTOR, null, "Scale local representation of the remote desktop on startup. " +
 				"The value is interpreted as scaling factor in percents. The default value of 100% " +
 				"corresponds to the original framebuffer size.");
-	}
+        parser.addOption(ARG_FULL_SCREEN, null, "Full screen mode. Possible values: yes/true and no/false. Default: no.");
+		parser.addOption(ARG_SSH_HOST, "", "SSH host name.");
+		parser.addOption(ARG_SSH_PORT, "0",
+				"SSH port number. When empty, standard SSH port number (" + ConnectionParams.DEFAULT_SSH_PORT + ") is used.");
+		parser.addOption(ARG_SSH_USER, "", "SSH user name.");
+        parser.addOption(ARG_ALLOW_APPLET_INTERACTIVE_CONNECTIONS, null, "Allow applet interactively connect to other hosts then in HostName param or hostbase. Possible values: yes/true, no/false. Default: false.");
+        parser.addOption(ARG_VERBOSE, null, "Verbose console output.");
+        parser.addOption(ARG_VERBOSE_MORE, null, "More verbose console output.");
 
-	public static void completeSettingsFromCLI(Parser parser, ConnectionParams connectionParams, ProtocolSettings rfbSettings, UiSettings uiSettings) {
-		completeSettings(parser.getValueFor(ARG_HOST), parser.getValueFor(ARG_PORT),
-				parser.getValueFor(ARG_SHOW_CONTROLS), parser.getValueFor(ARG_VIEW_ONLY),
-				parser.getValueFor(ARG_ALLOW_CLIPBOARD_TRANSFER), parser.getValueFor(ARG_REMOTE_CHARSET),
-				parser.getValueFor(ARG_ALLOW_COPY_RECT), parser.getValueFor(ARG_SHARE_DESKTOP),
-				parser.getValueFor(ARG_ENCODING), parser.getValueFor(ARG_COMPRESSION_LEVEL),
-				parser.getValueFor(ARG_JPEG_IMAGE_QUALITY), parser.getValueFor(ARG_COLOR_DEPTH),
-				parser.getValueFor(ARG_SCALING_FACTOR), parser.getValueFor(ARG_LOCAL_POINTER),
-				parser.getValueFor(ARG_CONVERT_TO_ASCII), connectionParams, rfbSettings, uiSettings);
+    }
+
+	public static int completeSettingsFromCLI(final Parser parser, ConnectionParams connectionParams, ProtocolSettings rfbSettings, UiSettings uiSettings) {
+		int mask = completeSettings(
+				new ParamsRetriever() {
+					@Override
+					public String getParamByName(String name) {
+						return parser.getValueFor(name);
+					}
+				},
+				connectionParams, rfbSettings, uiSettings);
 		// when hostName == a.b.c.d:3 where :3 is display num (X Window) we need add display num to port number
 		if ( ! Strings.isTrimmedEmpty(connectionParams.hostName)) {
 			splitConnectionParams(connectionParams, connectionParams.hostName);
@@ -125,9 +131,14 @@
 		if (parser.isSetPlainOptions()) {
 			splitConnectionParams(connectionParams, parser.getPlainOptionAt(0));
 			if (parser.getPlainOptionsNumber() > 1) {
-				connectionParams.parsePortNumber(parser.getPlainOptionAt(1));
-			}
+                try {
+                    connectionParams.parseRfbPortNumber(parser.getPlainOptionAt(1));
+                } catch (WrongParameterException e) {
+                    //nop
+                }
+            }
 		}
+        return mask;
 	}
 
 
@@ -142,55 +153,96 @@
 			String[] splitted = host.split(":");
 			connectionParams.hostName = splitted[0];
 			if (splitted.length > 1) {
-				connectionParams.parsePortNumber(splitted[splitted.length - 1]);
-			}
+                try {
+                    connectionParams.parseRfbPortNumber(splitted[splitted.length - 1]);
+                } catch (WrongParameterException e) {
+                    //nop
+                }
+            }
 		} else {
 			connectionParams.hostName = host;
 		}
 	}
 
-	private static void completeSettings(String hostName, String portNumber,
-	                                     String showControlsParam, String viewOnlyParam, String allowClipboardTransfer,
-	                                     String remoteCharsetName,
-	                                     String allowCopyRectParam,
-	                                     String shareDesktopParam, String encodingParam, String compressionLevelParam,
-	                                     String jpegQualityParam, String colorDepthParam, String scaleFactorParam,
-	                                     String localPointerParam,
-	                                     String convertToAsciiParam, ConnectionParams connectionParams, ProtocolSettings rfbSettings, UiSettings uiSettings) {
+	interface ParamsRetriever {
+		String getParamByName(String name);
+	}
+	private static int completeSettings(ParamsRetriever pr, ConnectionParams connectionParams, ProtocolSettings rfbSettings, UiSettings uiSettings) {
+		String hostName = pr.getParamByName(ARG_HOST);
+		String portNumber = pr.getParamByName(ARG_PORT);
+		String showControlsParam = pr.getParamByName(ARG_SHOW_CONTROLS);
+		String viewOnlyParam = pr.getParamByName(ARG_VIEW_ONLY);
+		String allowClipboardTransfer = pr.getParamByName(ARG_ALLOW_CLIPBOARD_TRANSFER);
+		String remoteCharsetName = pr.getParamByName(ARG_REMOTE_CHARSET);
+		String allowCopyRectParam = pr.getParamByName(ARG_ALLOW_COPY_RECT);
+		String shareDesktopParam = pr.getParamByName(ARG_SHARE_DESKTOP);
+		String encodingParam = pr.getParamByName(ARG_ENCODING);
+		String compressionLevelParam = pr.getParamByName(ARG_COMPRESSION_LEVEL);
+		String jpegQualityParam = pr.getParamByName(ARG_JPEG_IMAGE_QUALITY);
+		String colorDepthParam = pr.getParamByName(ARG_COLOR_DEPTH);
+		String scaleFactorParam = pr.getParamByName(ARG_SCALING_FACTOR);
+        String fullScreenParam = pr.getParamByName(ARG_FULL_SCREEN);
+		String localPointerParam = pr.getParamByName(ARG_LOCAL_POINTER);
+		String convertToAsciiParam = pr.getParamByName(ARG_CONVERT_TO_ASCII);
+		String sshHostNameParam = pr.getParamByName(ARG_SSH_HOST);
+		String sshPortNumberParam = pr.getParamByName(ARG_SSH_PORT);
+		String sshUserNameParam = pr.getParamByName(ARG_SSH_USER);
+
 		connectionParams.hostName = hostName;
-		try {
-			connectionParams.portNumber = Integer.parseInt(portNumber);
-		} catch (NumberFormatException e) { /* nop */ }
+        try {
+            connectionParams.parseRfbPortNumber(portNumber);
+        } catch (WrongParameterException e) {
+            //nop
+        }
 
-		showControls = parseBooleanOrDefault(showControlsParam, true);
+        connectionParams.sshHostName = sshHostNameParam;
+		connectionParams.setUseSsh( ! Strings.isTrimmedEmpty(sshHostNameParam));
+		connectionParams.parseSshPortNumber(sshPortNumberParam);
+		connectionParams.sshUserName = sshUserNameParam;
+
+        int rfbMask = 0;
+		uiSettings.showControls = parseBooleanOrDefault(showControlsParam, true);
+        allowAppletInteractiveConnections =
+                parseBooleanOrDefault(pr.getParamByName(ARG_ALLOW_APPLET_INTERACTIVE_CONNECTIONS), false);
 		rfbSettings.setViewOnly(parseBooleanOrDefault(viewOnlyParam, false));
+        if (isGiven(viewOnlyParam)) rfbMask |= ProtocolSettings.CHANGED_VIEW_ONLY;
 		rfbSettings.setAllowClipboardTransfer(parseBooleanOrDefault(allowClipboardTransfer, true));
-		rfbSettings.setRemoteCharsetName(remoteCharsetName);
-	    rfbSettings.setAllowCopyRect(parseBooleanOrDefault(allowCopyRectParam, true));
-		rfbSettings.setSharedFlag(parseBooleanOrDefault(shareDesktopParam, true));
-		rfbSettings.setConvertToAscii(parseBooleanOrDefault(convertToAsciiParam, false));
-		if (EncodingType.TIGHT.getName().equalsIgnoreCase(encodingParam)) {
+        if (isGiven(allowClipboardTransfer)) rfbMask |= ProtocolSettings.CHANGED_ALLOW_CLIPBOARD_TRANSFER;
+        rfbSettings.setRemoteCharsetName(remoteCharsetName);
+        rfbSettings.setAllowCopyRect(parseBooleanOrDefault(allowCopyRectParam, true));
+        if (isGiven(allowCopyRectParam)) rfbMask |= ProtocolSettings.CHANGED_ALLOW_COPY_RECT;
+        rfbSettings.setSharedFlag(parseBooleanOrDefault(shareDesktopParam, true));
+        if (isGiven(shareDesktopParam)) rfbMask |= ProtocolSettings.CHANGED_SHARED;
+        rfbSettings.setConvertToAscii(parseBooleanOrDefault(convertToAsciiParam, false));
+        if (isGiven(convertToAsciiParam)) rfbMask |= ProtocolSettings.CHANGED_CONVERT_TO_ASCII;
+        if (EncodingType.TIGHT.getName().equalsIgnoreCase(encodingParam)) {
 			rfbSettings.setPreferredEncoding(EncodingType.TIGHT);
+            rfbMask |= ProtocolSettings.CHANGED_ENCODINGS;
 		}
 		if (EncodingType.HEXTILE.getName().equalsIgnoreCase(encodingParam)) {
 			rfbSettings.setPreferredEncoding(EncodingType.HEXTILE);
+            rfbMask |= ProtocolSettings.CHANGED_ENCODINGS;
 		}
 		if (EncodingType.ZRLE.getName().equalsIgnoreCase(encodingParam)) {
 			rfbSettings.setPreferredEncoding(EncodingType.ZRLE);
+            rfbMask |= ProtocolSettings.CHANGED_ENCODINGS;
 		}
 		if (EncodingType.RAW_ENCODING.getName().equalsIgnoreCase(encodingParam)) {
 			rfbSettings.setPreferredEncoding(EncodingType.RAW_ENCODING);
+            rfbMask |= ProtocolSettings.CHANGED_ENCODINGS;
 		}
-		try {
+        try {
 			int compLevel = Integer.parseInt(compressionLevelParam);
 			if (compLevel > 0 && compLevel <= 9) {
 				rfbSettings.setCompressionLevel(compLevel);
+                rfbMask |= ProtocolSettings.CHANGED_COMPRESSION_LEVEL;
 			}
 		} catch (NumberFormatException e) { /* nop */ }
 		try {
 			int jpegQuality = Integer.parseInt(jpegQualityParam);
 			if (jpegQuality > 0 && jpegQuality <= 9) {
 				rfbSettings.setJpegQuality(jpegQuality);
+                rfbMask |= ProtocolSettings.CHANGED_JPEG_QUALITY;
 			}
 		} catch (NumberFormatException e) {
 			if ("lossless".equalsIgnoreCase(jpegQualityParam)) {
@@ -199,63 +251,67 @@
 		}
 		try {
 			int colorDepth = Integer.parseInt(colorDepthParam);
-			rfbSettings.setBitsPerPixel(colorDepth);
+			rfbSettings.setColorDepth(colorDepth);
+            rfbMask |= ProtocolSettings.CHANGED_COLOR_DEPTH;
 		} catch (NumberFormatException e) { /* nop */ }
+        int uiMask = 0;
 		if (scaleFactorParam != null) {
 			try {
 				int scaleFactor = Integer.parseInt(scaleFactorParam.replaceAll("\\D", ""));
 				if (scaleFactor >= 10 && scaleFactor <= 200) {
 					uiSettings.setScalePercent(scaleFactor);
+                    uiMask |= UiSettings.CHANGED_SCALE_FACTOR;
 				}
 			} catch (NumberFormatException e) { /* nop */ }
 		}
+        uiSettings.setFullScreen(parseBooleanOrDefault(fullScreenParam, false));
+        if (isGiven(fullScreenParam)) uiMask |= UiSettings.CHANGED_FULL_SCREEN;
 
-		if ("on".equalsIgnoreCase(localPointerParam) ||
+        if ("on".equalsIgnoreCase(localPointerParam) ||
 			"true".equalsIgnoreCase(localPointerParam) ||
 			"yes".equalsIgnoreCase(localPointerParam)) {
 				rfbSettings.setMouseCursorTrack(LocalPointer.ON);
+                rfbMask |= ProtocolSettings.CHANGED_MOUSE_CURSOR_TRACK;
 		}
 		if ("off".equalsIgnoreCase(localPointerParam) ||
 			"no".equalsIgnoreCase(localPointerParam) ||
 			"false".equalsIgnoreCase(localPointerParam)) {
 				rfbSettings.setMouseCursorTrack(LocalPointer.OFF);
+                rfbMask |= ProtocolSettings.CHANGED_MOUSE_CURSOR_TRACK;
 		}
 		if ("hide".equalsIgnoreCase(localPointerParam) ||
 			"hidden".equalsIgnoreCase(localPointerParam)) {
 				rfbSettings.setMouseCursorTrack(LocalPointer.HIDE);
+                rfbMask |= ProtocolSettings.CHANGED_MOUSE_CURSOR_TRACK;
 		}
+        return (uiMask << 16) | rfbMask;
 	}
 
+    private static boolean isGiven(String param) {
+        return ! Strings.isTrimmedEmpty(param);
+    }
+
 	static boolean parseBooleanOrDefault(String param, boolean defaultValue) {
 		return defaultValue ?
 				! ("no".equalsIgnoreCase(param) || "false".equalsIgnoreCase(param)) :
 				"yes".equalsIgnoreCase(param) || "true".equalsIgnoreCase(param);
 	}
 
-	public static void completeSettingsFromApplet(JApplet applet,
+	public static int completeSettingsFromApplet(final JApplet applet,
 			ConnectionParams connectionParams, ProtocolSettings rfbSettings, UiSettings uiSettings) {
-
-		String host = applet.getParameter(ARG_HOST);
-		if (Strings.isTrimmedEmpty(host)) {
-			host = applet.getCodeBase().getHost();
-		}
-		completeSettings(host,
-				applet.getParameter(ARG_PORT),
-				applet.getParameter(ARG_SHOW_CONTROLS),
-				applet.getParameter(ARG_VIEW_ONLY),
-				applet.getParameter(ARG_ALLOW_CLIPBOARD_TRANSFER),
-				applet.getParameter(ARG_REMOTE_CHARSET),
-				applet.getParameter(ARG_ALLOW_COPY_RECT),
-				applet.getParameter(ARG_SHARE_DESKTOP),
-				applet.getParameter(ARG_ENCODING),
-				applet.getParameter(ARG_COMPRESSION_LEVEL),
-				applet.getParameter(ARG_JPEG_IMAGE_QUALITY),
-				applet.getParameter(ARG_COLOR_DEPTH),
-				applet.getParameter(ARG_SCALING_FACTOR),
-				applet.getParameter(ARG_LOCAL_POINTER),
-				applet.getParameter(ARG_CONVERT_TO_ASCII),
-				connectionParams, rfbSettings, uiSettings);
-		isSeparateFrame = parseBooleanOrDefault(applet.getParameter(ARG_OPEN_NEW_WINDOW), true);
+        isSeparateFrame = parseBooleanOrDefault(applet.getParameter(ARG_OPEN_NEW_WINDOW), true);
+        final int paramsMask = completeSettings(
+                new ParamsRetriever() {
+                    @Override
+                    public String getParamByName(String name) {
+                        return applet.getParameter(name);
+                    }
+                },
+                connectionParams, rfbSettings, uiSettings);
+        if ( ! allowAppletInteractiveConnections && connectionParams.isHostNameEmpty()) {
+            connectionParams.hostName = applet.getCodeBase().getHost();
+        }
+        return paramsMask;
 	}
 
 
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/RendererImpl.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/RendererImpl.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -37,7 +37,8 @@
 import java.util.concurrent.TimeoutException;
 
 public class RendererImpl extends Renderer implements ImageObserver {
-	private final Image offscreanImage;
+    CyclicBarrier barrier = new CyclicBarrier(2);
+    private final Image offscreanImage;
 	public RendererImpl(Reader reader, int width, int height, PixelFormat pixelFormat) {
 		if (0 == width) width = 1;
 		if (0 == height) height = 1;
@@ -61,7 +62,6 @@
 	 * @param jpegBufferLength jpeg image data array length
 	 * @param rect image location and dimensions
 	 */
-	CyclicBarrier barier = new CyclicBarrier(2);
 	@Override
 	public void drawJpegImage(byte[] bytes, int offset, int jpegBufferLength,
 			FramebufferUpdateRectangle rect) {
@@ -69,7 +69,7 @@
 				offset, jpegBufferLength);
 		Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
 		try {
-			barier.await(3, TimeUnit.SECONDS);
+			barrier.await(3, TimeUnit.SECONDS);
 		} catch (InterruptedException e) {
 			// nop
 		} catch (BrokenBarrierException e) {
@@ -87,7 +87,7 @@
 		boolean isReady = (infoflags & (ALLBITS | ABORT)) != 0;
 		if (isReady) {
 			try {
-				barier.await();
+				barrier.await();
 			} catch (InterruptedException e) {
 				// nop
 			} catch (BrokenBarrierException e) {
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/SoftCursorImpl.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SoftCursorImpl.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/Surface.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/Surface.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -33,7 +33,7 @@
 import com.glavsoft.rfb.protocol.ProtocolContext;
 import com.glavsoft.rfb.protocol.ProtocolSettings;
 import com.glavsoft.transport.Reader;
-import com.glavsoft.viewer.Viewer;
+import com.glavsoft.viewer.UiSettings;
 
 import javax.swing.*;
 import java.awt.*;
@@ -51,8 +51,9 @@
 	private ModifierButtonEventListener modifierButtonListener;
 	private boolean isUserInputEnabled = false;
 	private final ProtocolContext context;
-	private double scaleFactor;
-	private final Viewer viewer;
+    private SwingViewerWindow viewerWindow;
+    private double scaleFactor;
+	public Dimension oldSize;
 
 	@Override
 	public boolean isDoubleBuffered() {
@@ -61,19 +62,25 @@
 		return false;
 	}
 
-	public Surface(ProtocolContext context, Viewer viewer, double scaleFactor) {
+	public Surface(ProtocolContext context, double scaleFactor, LocalMouseCursorShape mouseCursorShape) {
 		this.context = context;
-		this.viewer = viewer;
 		this.scaleFactor = scaleFactor;
 		init(context.getFbWidth(), context.getFbHeight());
+		oldSize = getPreferredSize();
 
 		if ( ! context.getSettings().isViewOnly()) {
 			setUserInputEnabled(true, context.getSettings().isConvertToAscii());
 		}
 		showCursor = context.getSettings().isShowRemoteCursor();
+        setLocalCursorShape(mouseCursorShape);
 	}
 
-	private void setUserInputEnabled(boolean enable, boolean convertToAscii) {
+    // TODO Extract abstract/interface ViewerWindow from SwingViewerWindow
+    public void setViewerWindow(SwingViewerWindow viewerWindow) {
+        this.viewerWindow = viewerWindow;
+    }
+
+    private void setUserInputEnabled(boolean enable, boolean convertToAscii) {
 		if (enable == isUserInputEnabled) return;
 		isUserInputEnabled = enable;
 		if (enable) {
@@ -105,7 +112,7 @@
 	@Override
 	public Renderer createRenderer(Reader reader, int width, int height, PixelFormat pixelFormat) {
 		renderer = new RendererImpl(reader, width, height, pixelFormat);
-		synchronized (renderer) {
+		synchronized (renderer.getLock()) {
 			cursor = renderer.getCursor();
 		}
 		init(renderer.getWidth(), renderer.getHeight());
@@ -121,21 +128,22 @@
 
 	private void updateFrameSize() {
 		setSize(getPreferredSize());
-		viewer.packContainer();
+		viewerWindow.pack();
 		requestFocus();
 	}
 
 	@Override
 	public void paintComponent(Graphics g) {
+        if (null == renderer) return;
 		((Graphics2D)g).scale(scaleFactor, scaleFactor);
 		((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-		synchronized (renderer) {
+		synchronized (renderer.getLock()) {
 			Image offscreenImage = renderer.getOffscreenImage();
 			if (offscreenImage != null) {
 				g.drawImage(offscreenImage, 0, 0, null);
 			}
 		}
-		synchronized (cursor) {
+		synchronized (cursor.getLock()) {
 			Image cursorImage = cursor.getImage();
 			if (showCursor && cursorImage != null &&
 					(scaleFactor != 1 ||
@@ -179,7 +187,7 @@
 
 	@Override
 	public void repaintCursor() {
-		synchronized (cursor) {
+		synchronized (cursor.getLock()) {
 			repaint((int)(cursor.oldRX * scaleFactor), (int)(cursor.oldRY * scaleFactor),
 					(int)Math.ceil(cursor.oldWidth * scaleFactor) + 1, (int)Math.ceil(cursor.oldHeight * scaleFactor) + 1);
 			repaint((int)(cursor.rX * scaleFactor), (int)(cursor.rY * scaleFactor),
@@ -189,14 +197,14 @@
 
 	@Override
 	public void updateCursorPosition(short x, short y) {
-		synchronized (cursor) {
+		synchronized (cursor.getLock()) {
 			cursor.updatePosition(x, y);
 			repaintCursor();
 		}
 	}
 
 	private void showCursor(boolean show) {
-		synchronized (cursor) {
+		synchronized (cursor.getLock()) {
 			showCursor = show;
 		}
 	}
@@ -215,14 +223,26 @@
 			setUserInputEnabled( ! settings.isViewOnly(), settings.isConvertToAscii());
 			showCursor(settings.isShowRemoteCursor());
 		} else if (UiSettings.isUiSettingsChangedFired(e)) {
-			UiSettings settings = (UiSettings) e.getSource();
-			scaleFactor = settings.getScaleFactor();
+			UiSettings uiSettings = (UiSettings) e.getSource();
+			oldSize = getPreferredSize();
+			scaleFactor = uiSettings.getScaleFactor();
+            if (uiSettings.isChangedMouseCursorShape()) {
+                setLocalCursorShape(uiSettings.getMouseCursorShape());
+            }
 		}
 		mouseEventListener.setScaleFactor(scaleFactor);
 		updateFrameSize();
 	}
 
-	@Override
+    public void setLocalCursorShape(LocalMouseCursorShape cursorShape) {
+        if (LocalMouseCursorShape.SYSTEM_DEFAULT == cursorShape) {
+            setCursor(Cursor.getDefaultCursor());
+        } else {
+            setCursor(Utils.getCursor(cursorShape));
+        }
+    }
+
+    @Override
 	public void setPixelFormat(PixelFormat pixelFormat) {
 		if (renderer != null) {
 			renderer.initPixelFormat(pixelFormat);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SwingConnectionWorkerFactory.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,66 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.viewer.AbstractConnectionWorkerFactory;
+import com.glavsoft.viewer.ConnectionPresenter;
+import com.glavsoft.viewer.NetworkConnectionWorker;
+import com.glavsoft.viewer.RfbConnectionWorker;
+
+import javax.swing.*;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class SwingConnectionWorkerFactory extends AbstractConnectionWorkerFactory {
+
+    private JFrame parentWindow;
+    private String predefinedPassword;
+    private final ConnectionPresenter presenter;
+    private final SwingViewerWindowFactory viewerWindowFactory;
+
+    public SwingConnectionWorkerFactory(JFrame parentWindow, String predefinedPassword, ConnectionPresenter presenter,
+                                        SwingViewerWindowFactory viewerWindowFactory) {
+        this.parentWindow = parentWindow;
+        this.predefinedPassword = predefinedPassword;
+        this.presenter = presenter;
+        this.viewerWindowFactory = viewerWindowFactory;
+    }
+
+    @Override
+    public NetworkConnectionWorker createNetworkConnectionWorker() {
+        return new SwingNetworkConnectionWorker(parentWindow);
+    }
+
+    @Override
+    public RfbConnectionWorker createRfbConnectionWorker() {
+        return new SwingRfbConnectionWorker(predefinedPassword, presenter, parentWindow, viewerWindowFactory);
+    }
+
+    @Override
+    public void setPredefinedPassword(String predefinedPassword) {
+        this.predefinedPassword = predefinedPassword;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SwingNetworkConnectionWorker.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,181 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.viewer.*;
+import com.glavsoft.viewer.swing.ssh.SshConnectionManager;
+
+import javax.swing.*;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.AccessControlException;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+public class SwingNetworkConnectionWorker extends SwingWorker<Socket, String> implements NetworkConnectionWorker {
+    public static final int MAX_HOSTNAME_LENGTH_FOR_MESSAGES = 40;
+    private final JFrame parentWindow;
+    private Logger logger;
+    private boolean hasSshSupport;
+    private ConnectionParams connectionParams;
+    private ConnectionPresenter presenter;
+
+
+    public SwingNetworkConnectionWorker(JFrame parentWindow) {
+        this.parentWindow = parentWindow;
+        logger = Logger.getLogger(getClass().getName());
+    }
+
+    @Override
+    public Socket doInBackground() throws Exception {
+        String s = "<b>" +connectionParams.hostName + "</b>:" + connectionParams.getPortNumber();
+        if (connectionParams.useSsh()) {
+            s += " <i>(via ssh://" + connectionParams.sshUserName + "@" + connectionParams.sshHostName + ":" + connectionParams.getSshPortNumber() + ")</i>";
+        }
+
+        String message = "<html>Trying to connect to " + s + "</html>";
+        logger.info(message.replaceAll("<[^<>]+?>", ""));
+        publish(message);
+        int port;
+        String host;
+        if (hasSshSupport && connectionParams.useSsh()) {
+            SshConnectionManager sshConnectionManager = new SshConnectionManager(parentWindow);
+            message = "Creating SSH tunnel to " + connectionParams.sshHostName + ":" + connectionParams.getSshPortNumber();
+            logger.info(message);
+            publish(message);
+            port = sshConnectionManager.connect(connectionParams);
+            if (sshConnectionManager.isConnected() ) {
+                host = "127.0.0.1";
+                message = "SSH tunnel established: " + host + ":" + port;
+                logger.info(message);
+                publish(message);
+            } else {
+                throw new ConnectionErrorException("Could not create SSH tunnel: " + sshConnectionManager.getErrorMessage());
+            }
+        } else {
+            host = connectionParams.hostName;
+            port = connectionParams.getPortNumber();
+        }
+
+        message = "Connecting to host " + host + ":" + port + (connectionParams.useSsh() ? " (tunneled)" : "");
+        logger.info(message);
+        publish(message);
+
+        return new Socket(host, port);
+    }
+
+    private String formatHostString(String hostName) {
+        if (hostName.length() <= MAX_HOSTNAME_LENGTH_FOR_MESSAGES) {
+            return  hostName;
+        } else {
+            return hostName.substring(0, MAX_HOSTNAME_LENGTH_FOR_MESSAGES) + "...";
+        }
+    }
+
+    @Override
+    protected void process(List<String> strings) { // EDT
+        String message = strings.get(strings.size() - 1); // get last
+        presenter.showMessage(message);
+    }
+
+    @Override
+    protected void done() { // EDT
+        try {
+            final Socket socket = get();
+            presenter.successfulNetworkConnection(socket);
+        } catch (CancellationException e) {
+            logger.info("Cancelled");
+            presenter.showMessage("Cancelled");
+            presenter.connectionFailed();
+        } catch (InterruptedException e) {
+            logger.info("Interrupted");
+            presenter.showMessage("Interrupted");
+            presenter.connectionFailed();
+        } catch (ExecutionException e) {
+            String errorMessage = null;
+            try {
+                throw e.getCause();
+            } catch (UnknownHostException uhe) {
+                logger.severe("Unknown host: " + connectionParams.hostName);
+                errorMessage = "Unknown host: '" + formatHostString(connectionParams.hostName) + "'";
+            } catch (IOException ioe) {
+                logger.severe("Couldn't connect to '" + connectionParams.hostName +
+                        ":" + connectionParams.getPortNumber() + "':\n" + ioe.getMessage());
+                logger.log(Level.FINEST, "Couldn't connect to '" + connectionParams.hostName +
+                        ":" + connectionParams.getPortNumber() + "':\n" + ioe.getMessage(), ioe);
+                errorMessage = "Couldn't connect to '" + formatHostString(connectionParams.hostName) +
+                        ":" + connectionParams.getPortNumber() + "':\n" + ioe.getMessage();
+            } catch (CancelConnectionException cce) {
+                logger.severe("Cancelled: " + cce.getMessage());
+            } catch (AccessControlException ace) {
+                logger.severe("Couldn't connect to: " +
+                        connectionParams.hostName + ":" + connectionParams.getPortNumber() +
+                        ": " + ace.getMessage());
+                logger.log(Level.FINEST, "Couldn't connect to: " +
+                        connectionParams.hostName + ":" + connectionParams.getPortNumber() +
+                        ": " + ace.getMessage(), ace);
+                errorMessage = "Access control error";
+            } catch (ConnectionErrorException cee) {
+                logger.severe(cee.getMessage() + " host: " +
+                        connectionParams.hostName + ":" + connectionParams.getPortNumber());
+                errorMessage = cee.getMessage() + "\nHost: " +
+                    formatHostString(connectionParams.hostName) + ":" + connectionParams.getPortNumber();
+            } catch (Throwable throwable) {
+                logger.log(Level.FINEST, "Couldn't connect to '" + formatHostString(connectionParams.hostName) +
+                        ":" + connectionParams.getPortNumber() + "':\n" + throwable.getMessage(), throwable);
+                errorMessage = "Couldn't connect to '" + formatHostString(connectionParams.hostName) +
+                        ":" + connectionParams.getPortNumber() + "':\n" + throwable.getMessage();
+            }
+            presenter.showConnectionErrorDialog(errorMessage);
+            presenter.clearMessage();
+            presenter.connectionFailed();
+        }
+    }
+
+    @Override
+    public void setConnectionParams(ConnectionParams connectionParams) {
+        this.connectionParams = connectionParams;
+    }
+
+    @Override
+    public void setPresenter(ConnectionPresenter presenter) {
+        this.presenter = presenter;
+    }
+
+    @Override
+    public void setHasSshSupport(boolean hasSshSupport) {
+        this.hasSshSupport = hasSshSupport;
+    }
+
+    @Override
+    public boolean cancel() {
+        return super.cancel(true);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SwingRfbConnectionWorker.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,272 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.exceptions.*;
+import com.glavsoft.rfb.IPasswordRetriever;
+import com.glavsoft.rfb.IRfbSessionListener;
+import com.glavsoft.rfb.protocol.Protocol;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.*;
+import com.glavsoft.viewer.swing.gui.PasswordDialog;
+
+import javax.swing.*;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.Socket;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
+
+/**
+* @author dime at tightvnc.com
+*/
+public class SwingRfbConnectionWorker extends SwingWorker<Void, String> implements RfbConnectionWorker, IRfbSessionListener {
+
+    private String predefinedPassword;
+    private ConnectionPresenter presenter;
+    private JFrame parentWindow;
+    private SwingViewerWindowFactory viewerWindowFactory;
+    private Logger logger;
+    private volatile boolean isStoppingProcess;
+    private SwingViewerWindow viewerWindow;
+    protected String connectionString;
+    protected Protocol workingProtocol;
+    protected Socket workingSocket;
+    protected ProtocolSettings rfbSettings;
+    protected UiSettings uiSettings;
+
+    @Override
+    public Void doInBackground() throws Exception {
+        if (null == workingSocket) throw new ConnectionErrorException("Null socket");
+        workingSocket.setTcpNoDelay(true); // disable Nagle algorithm
+        Reader reader = new Reader(workingSocket.getInputStream());
+        Writer writer = new Writer(workingSocket.getOutputStream());
+
+        workingProtocol = new Protocol(reader, writer,
+                new PasswordChooser(connectionString, parentWindow, this),
+                rfbSettings);
+        String message = "Handshaking with remote host";
+        logger.info(message);
+        publish(message);
+
+        workingProtocol.handshake();
+//      tryAgain = false;
+        return null;
+    }
+
+    public SwingRfbConnectionWorker(String predefinedPassword, ConnectionPresenter presenter, JFrame parentWindow,
+                                    SwingViewerWindowFactory viewerWindowFactory) {
+        this.predefinedPassword = predefinedPassword;
+        this.presenter = presenter;
+        this.parentWindow = parentWindow;
+        this.viewerWindowFactory = viewerWindowFactory;
+        logger = Logger.getLogger(getClass().getName());
+    }
+
+
+    @Override
+    protected void process(List<String> strings) { // EDT
+        String message = strings.get(strings.size() - 1); // get last
+        presenter.showMessage(message);
+    }
+
+    @Override
+    protected void done() { // EDT
+        try {
+            get();
+            presenter.showMessage("Handshake established");
+            ClipboardControllerImpl clipboardController =
+                    new ClipboardControllerImpl(workingProtocol, rfbSettings.getRemoteCharsetName());
+            clipboardController.setEnabled(rfbSettings.isAllowClipboardTransfer());
+            rfbSettings.addListener(clipboardController);
+            viewerWindow = viewerWindowFactory.createViewerWindow(
+                    workingProtocol, rfbSettings, uiSettings, connectionString, presenter);
+
+            workingProtocol.startNormalHandling(this, viewerWindow.getSurface(), clipboardController);
+            presenter.showMessage("Started");
+
+            presenter.successfulRfbConnection();
+        } catch (CancellationException e) {
+            logger.info("Cancelled");
+            presenter.showMessage("Cancelled");
+            presenter.connectionCancelled();
+        } catch (InterruptedException e) {
+            logger.info("Interrupted");
+            presenter.showMessage("Interrupted");
+            presenter.connectionFailed();
+        } catch (ExecutionException ee) {
+            String errorTitle;
+            String errorMessage;
+            try {
+                throw ee.getCause();
+            } catch (UnsupportedProtocolVersionException e) {
+                errorTitle = "Unsupported Protocol Version";
+                errorMessage = e.getMessage();
+                logger.severe(errorMessage);
+            } catch (UnsupportedSecurityTypeException e) {
+                errorTitle = "Unsupported Security Type";
+                errorMessage = e.getMessage();
+                logger.severe(errorMessage);
+            } catch (AuthenticationFailedException e) {
+                errorTitle = "Authentication Failed";
+                errorMessage = e.getMessage();
+                logger.severe(errorMessage);
+                presenter.clearPredefinedPassword();
+            } catch (TransportException e) {
+//            if ( ! isAppletStopped) {
+                errorTitle = "Connection Error";
+                errorMessage = "Connection Error: " + e.getMessage();
+                logger.severe(errorMessage);
+//            }
+            } catch (IOException e) {
+                errorTitle = "Connection Error";
+                errorMessage = "Connection Error: " + e.getMessage();
+                logger.severe(errorMessage);
+            } catch (FatalException e) {
+                errorTitle = "Connection Error";
+                errorMessage = "Connection Error: " + e.getMessage();
+                logger.severe(errorMessage);
+            } catch (Throwable e) {
+                errorTitle = "Error";
+                errorMessage = "Error: " + e.getMessage();
+                logger.severe(errorMessage);
+            }
+            presenter.showReconnectDialog(errorTitle, errorMessage);
+            presenter.clearMessage();
+            presenter.connectionFailed();
+        }
+    }
+
+    @Override
+	public void rfbSessionStopped(final String reason) {
+        if (workingProtocol != null) {
+			workingProtocol.cleanUpSession();
+		}
+		if (isStoppingProcess) return;
+		cleanUpUISessionAndConnection();
+        logger.info("Rfb session stopped: " + reason);
+        if (presenter.needReconnection()) {
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    presenter.showReconnectDialog("Connection error", reason);
+                    presenter.reconnect(predefinedPassword);
+                }
+            });
+        }
+	}
+
+    @Override
+    public boolean cancel() {
+        boolean res = super.cancel(true);
+        if (res && workingProtocol != null) {
+            workingProtocol.cleanUpSession();
+        }
+        cleanUpUISessionAndConnection();
+        return res;
+    }
+
+    private synchronized void cleanUpUISessionAndConnection() {
+		isStoppingProcess = true;
+		if (workingSocket != null && workingSocket.isConnected()) {
+			try {
+				workingSocket.close();
+			} catch (IOException e) { /*nop*/ }
+		}
+		if (viewerWindow != null) {
+            viewerWindow.close();
+		}
+		isStoppingProcess = false;
+	}
+
+    @Override
+    public void setWorkingSocket(Socket workingSocket) {
+        this.workingSocket = workingSocket;
+    }
+
+    @Override
+    public void setRfbSettings(ProtocolSettings rfbSettings) {
+        this.rfbSettings = rfbSettings;
+    }
+
+    @Override
+    public void setUiSettings(UiSettings uiSettings) {
+        this.uiSettings = uiSettings;
+    }
+
+    @Override
+    public void setConnectionString(String connectionString) {
+        this.connectionString = connectionString;
+    }
+
+    /**
+     * Ask user for password if needed
+     */
+    private class PasswordChooser implements IPasswordRetriever {
+        PasswordDialog passwordDialog;
+        private String connectionString;
+        private final JFrame owner;
+        private final ConnectionWorker onCancel;
+
+        private PasswordChooser(String connectionString, JFrame parentWindow, ConnectionWorker onCancel) {
+            this.connectionString = connectionString;
+            this.owner = parentWindow;
+            this.onCancel = onCancel;
+        }
+
+        @Override
+        public String getPassword() {
+            return Strings.isTrimmedEmpty(predefinedPassword) ?
+                    getPasswordFromGUI() :
+                    predefinedPassword;
+        }
+
+        private String getPasswordFromGUI() {
+            try {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (null == passwordDialog) {
+                            passwordDialog = new PasswordDialog(owner, onCancel);
+                        }
+                        passwordDialog.setServerHostName(connectionString);
+                        passwordDialog.toFront();
+                        passwordDialog.setVisible(true);
+                    }
+                });
+            } catch (InterruptedException e) {
+                //nop
+            } catch (InvocationTargetException e) {
+                //nop
+            }
+            return passwordDialog.getPassword();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SwingViewerWindow.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,896 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.core.SettingsChangedEvent;
+import com.glavsoft.rfb.IChangeSettingsListener;
+import com.glavsoft.rfb.client.KeyEventMessage;
+import com.glavsoft.rfb.protocol.Protocol;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.utils.Keymap;
+import com.glavsoft.viewer.ConnectionPresenter;
+import com.glavsoft.viewer.UiSettings;
+import com.glavsoft.viewer.Viewer;
+import com.glavsoft.viewer.swing.gui.OptionsDialog;
+
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+public class SwingViewerWindow implements IChangeSettingsListener {
+	public static final int FS_SCROLLING_ACTIVE_BORDER = 20;
+	private JToggleButton zoomFitButton;
+	private JToggleButton zoomFullScreenButton;
+	private JButton zoomInButton;
+	private JButton zoomOutButton;
+	private JButton zoomAsIsButton;
+	private JPanel outerPanel;
+	private JScrollPane scroller;
+	private JFrame frame;
+	private boolean forceResizable = true;
+	private ButtonsBar buttonsBar;
+	private Surface surface;
+	private boolean isSeparateFrame;
+    private final boolean isApplet;
+    private Viewer viewer;
+    private String connectionString;
+    private ConnectionPresenter presenter;
+    private Rectangle oldContainerBounds;
+	private volatile boolean isFullScreen;
+	private Border oldScrollerBorder;
+	private JLayeredPane lpane;
+	private EmptyButtonsBarMouseAdapter buttonsBarMouseAdapter;
+    private String remoteDesktopName;
+    private ProtocolSettings rfbSettings;
+    private UiSettings uiSettings;
+    private Protocol workingProtocol;
+
+    private boolean isZoomToFitSelected;
+    private List<JComponent> kbdButtons;
+
+    public SwingViewerWindow(Protocol workingProtocol, ProtocolSettings rfbSettings, UiSettings uiSettings, Surface surface,
+                             boolean isSeparateFrame, boolean isApplet, Viewer viewer, String connectionString,
+                             ConnectionPresenter presenter) {
+        this.workingProtocol = workingProtocol;
+        this.rfbSettings = rfbSettings;
+        this.uiSettings = uiSettings;
+        this.surface = surface;
+        this.isSeparateFrame = isSeparateFrame;
+        this.isApplet = isApplet;
+        this.viewer = viewer;
+        this.connectionString = connectionString;
+        this.presenter = presenter;
+        createContainer(surface, isApplet, viewer);
+
+        if (uiSettings.showControls) {
+            createButtonsPanel(workingProtocol, isSeparateFrame? frame: viewer);
+            if (isSeparateFrame) registerResizeListener(frame);
+            updateZoomButtonsState();
+        }
+        if (uiSettings.isFullScreen()) {
+            switchOnFullscreenMode();
+        }
+        setSurfaceToHandleKbdFocus();
+        if (isSeparateFrame) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // nop
+                    }
+                    SwingUtilities.invokeLater(new Runnable() {
+                        @Override
+                        public void run() {
+                            frame.toFront();
+                        }
+                    });
+                }
+            }).start();
+        }
+	}
+
+	private void createContainer(final Surface surface, boolean isApplet, JApplet appletWindow) {
+		outerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)) {
+			@Override
+			public Dimension getSize() {
+				return surface.getPreferredSize();
+			}
+			@Override
+			public Dimension getPreferredSize() {
+				return surface.getPreferredSize();
+			}
+		};
+        outerPanel.setBackground(Color.DARK_GRAY);
+		lpane = new JLayeredPane() {
+			@Override
+			public Dimension getSize() {
+				return surface.getPreferredSize();
+			}
+			@Override
+			public Dimension getPreferredSize() {
+				return surface.getPreferredSize();
+			}
+		};
+		lpane.setPreferredSize(surface.getPreferredSize());
+		lpane.add(surface, JLayeredPane.DEFAULT_LAYER, 0);
+		outerPanel.add(lpane);
+
+		scroller = new JScrollPane(outerPanel);
+		if (isSeparateFrame) {
+			frame = new JFrame();
+			if ( ! isApplet) {
+				frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+			}
+            frame.setModalExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
+			Utils.setApplicationIconsForWindow(frame);
+            frame.setLayout(new BorderLayout(0, 0));
+            frame.add(scroller, BorderLayout.CENTER);
+
+//			frame.pack();
+            outerPanel.setSize(surface.getPreferredSize());
+            internalPack(null);
+            frame.setVisible(true);
+            frame.validate();
+		} else {
+            appletWindow.setLayout(new BorderLayout(0, 0));
+            appletWindow.add(scroller, BorderLayout.CENTER);
+            appletWindow.validate();
+		}
+	}
+
+	public void pack() {
+		final Dimension outerPanelOldSize = outerPanel.getSize();
+		outerPanel.setSize(surface.getPreferredSize());
+		if (isSeparateFrame && ! isZoomToFitSelected()) {
+			internalPack(outerPanelOldSize);
+		}
+        if (buttonsBar != null) {
+            updateZoomButtonsState();
+        }
+        updateWindowTitle();
+	}
+
+    public boolean isZoomToFitSelected() {
+        return isZoomToFitSelected;
+    }
+
+    public void setZoomToFitSelected(boolean zoomToFitSelected) {
+        isZoomToFitSelected = zoomToFitSelected;
+    }
+
+    public void setRemoteDesktopName(String name) {
+        remoteDesktopName = name;
+        updateWindowTitle();
+    }
+
+    private void updateWindowTitle() {
+        if (isSeparateFrame) {
+			frame.setTitle(remoteDesktopName + " [zoom: " + uiSettings.getScalePercentFormatted() + "%]");
+		}
+    }
+
+	private void internalPack(Dimension outerPanelOldSize) {
+		final Rectangle workareaRectangle = getWorkareaRectangle();
+		if (workareaRectangle.equals(frame.getBounds())) {
+			forceResizable = true;
+		}
+		final boolean isHScrollBar = scroller.getHorizontalScrollBar().isShowing() && ! forceResizable;
+		final boolean isVScrollBar = scroller.getVerticalScrollBar().isShowing() && ! forceResizable;
+
+		boolean isWidthChangeable = true;
+		boolean isHeightChangeable = true;
+		if (outerPanelOldSize != null && surface.oldSize != null) {
+			isWidthChangeable = forceResizable ||
+					(outerPanelOldSize.width == surface.oldSize.width && ! isHScrollBar);
+			isHeightChangeable = forceResizable ||
+					(outerPanelOldSize.height == surface.oldSize.height && ! isVScrollBar);
+		}
+		forceResizable = false;
+		frame.validate();
+
+		final Insets containerInsets = frame.getInsets();
+		Dimension preferredSize = frame.getPreferredSize();
+		Rectangle preferredRectangle = new Rectangle(frame.getLocation(), preferredSize);
+
+		if (null == outerPanelOldSize && workareaRectangle.contains(preferredRectangle)) {
+			frame.pack();
+		} else {
+			Dimension minDimension = new Dimension(
+					containerInsets.left + containerInsets.right, containerInsets.top + containerInsets.bottom);
+			if (buttonsBar != null && buttonsBar.isVisible) {
+				minDimension.width += buttonsBar.getWidth();
+				minDimension.height += buttonsBar.getHeight();
+			}
+			Dimension dim = new Dimension(preferredSize);
+			Point location = frame.getLocation();
+			if ( ! isWidthChangeable) {
+				dim.width = frame.getWidth();
+			} else {
+				if (isVScrollBar) dim.width += scroller.getVerticalScrollBar().getWidth();
+				if (dim.width < minDimension.width) dim.width = minDimension.width;
+
+				int dx = location.x - workareaRectangle.x;
+				if (dx < 0) {
+					dx = 0;
+					location.x = workareaRectangle.x;
+				}
+				int w = workareaRectangle.width - dx;
+				if (w < dim.width) {
+					int dw = dim.width - w;
+					if (dw < dx) {
+						location.x -= dw;
+					} else {
+						dim.width = workareaRectangle.width;
+						location.x = workareaRectangle.x;
+					}
+				}
+			}
+			if ( ! isHeightChangeable) {
+				dim.height = frame.getHeight();
+			} else {
+
+				if (isHScrollBar) dim.height += scroller.getHorizontalScrollBar().getHeight();
+				if (dim.height < minDimension.height) dim.height = minDimension.height;
+
+				int dy = location.y - workareaRectangle.y;
+				if (dy < 0) {
+					dy = 0;
+					location.y = workareaRectangle.y;
+				}
+				int h = workareaRectangle.height - dy;
+				if (h < dim.height) {
+					int dh = dim.height - h;
+					if (dh < dy) {
+						location.y -= dh;
+					} else {
+						dim.height = workareaRectangle.height;
+						location.y = workareaRectangle.y;
+					}
+				}
+			}
+			if ( ! location.equals(frame.getLocation())) {
+				frame.setLocation(location);
+			}
+			if ( ! isFullScreen ) {
+				frame.setSize(dim);
+			}
+		}
+		scroller.revalidate();
+	}
+
+	private Rectangle getWorkareaRectangle() {
+		final GraphicsConfiguration graphicsConfiguration = frame.getGraphicsConfiguration();
+		final Rectangle screenBounds = graphicsConfiguration.getBounds();
+		final Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration);
+
+		screenBounds.x += screenInsets.left;
+		screenBounds.y += screenInsets.top;
+		screenBounds.width -= screenInsets.left + screenInsets.right;
+		screenBounds.height -= screenInsets.top + screenInsets.bottom;
+		return screenBounds;
+	}
+
+	void addZoomButtons() {
+		buttonsBar.createStrut();
+		zoomOutButton = buttonsBar.createButton("zoom-out", "Zoom Out", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				zoomFitButton.setSelected(false);
+                uiSettings.zoomOut();
+			}
+		});
+		zoomInButton = buttonsBar.createButton("zoom-in", "Zoom In", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				zoomFitButton.setSelected(false);
+                uiSettings.zoomIn();
+			}
+		});
+		zoomAsIsButton = buttonsBar.createButton("zoom-100", "Zoom 100%", new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				zoomFitButton.setSelected(false);
+				forceResizable = false;
+                uiSettings.zoomAsIs();
+			}
+		});
+
+		zoomFitButton = buttonsBar.createToggleButton("zoom-fit", "Zoom to Fit Window",
+				new ItemListener() {
+					@Override
+					public void itemStateChanged(ItemEvent e) {
+						if (e.getStateChange() == ItemEvent.SELECTED) {
+							setZoomToFitSelected(true);
+							forceResizable = true;
+							zoomToFit();
+							updateZoomButtonsState();
+						} else {
+							setZoomToFitSelected(false);
+						}
+						setSurfaceToHandleKbdFocus();
+					}
+				});
+
+		zoomFullScreenButton = buttonsBar.createToggleButton("zoom-fullscreen", "Full Screen",
+			new ItemListener() {
+				@Override
+				public void itemStateChanged(ItemEvent e) {
+					updateZoomButtonsState();
+					if (e.getStateChange() == ItemEvent.SELECTED) {
+                        uiSettings.setFullScreen(switchOnFullscreenMode());
+					} else {
+						switchOffFullscreenMode();
+                        uiSettings.setFullScreen(false);
+					}
+					setSurfaceToHandleKbdFocus();
+				}
+			});
+			if ( ! isSeparateFrame) {
+				zoomFullScreenButton.setEnabled(false);
+				zoomFitButton.setEnabled(false);
+			}
+		}
+
+    protected void setSurfaceToHandleKbdFocus() {
+        if (surface != null && ! surface.requestFocusInWindow()) {
+            surface.requestFocus();
+        }
+    }
+
+    boolean switchOnFullscreenMode() {
+		zoomFullScreenButton.setSelected(true);
+		oldContainerBounds = frame.getBounds();
+		setButtonsBarVisible(false);
+		forceResizable = true;
+		frame.dispose();
+		frame.setUndecorated(true);
+		frame.setResizable(false);
+		frame.setVisible(true);
+		try {
+			frame.getGraphicsConfiguration().getDevice().setFullScreenWindow(frame);
+			isFullScreen = true;
+			scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
+			scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+			oldScrollerBorder = scroller.getBorder();
+			scroller.setBorder(new EmptyBorder(0, 0, 0, 0));
+			new FullscreenBorderDetectionThread(frame).start();
+		} catch (Exception ex) {
+            Logger.getLogger(this.getClass().getName()).info("Cannot switch into FullScreen mode: " + ex.getMessage());
+			return false;
+		}
+        return true;
+	}
+
+	private void switchOffFullscreenMode() {
+		if (isFullScreen) {
+			zoomFullScreenButton.setSelected(false);
+			isFullScreen = false;
+			setButtonsBarVisible(true);
+			try {
+				frame.dispose();
+				frame.setUndecorated(false);
+				frame.setResizable(true);
+				frame.getGraphicsConfiguration().getDevice().setFullScreenWindow(null);
+			} catch (Exception e) {
+				// nop
+			}
+			scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+			scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+			scroller.setBorder(oldScrollerBorder);
+			this.frame.setBounds(oldContainerBounds);
+			frame.setVisible(true);
+			pack();
+		}
+	}
+
+	private void zoomToFit() {
+		Dimension scrollerSize = scroller.getSize();
+		Insets scrollerInsets = scroller.getInsets();
+        uiSettings.zoomToFit(scrollerSize.width - scrollerInsets.left - scrollerInsets.right,
+                scrollerSize.height - scrollerInsets.top - scrollerInsets.bottom +
+                        (isFullScreen ? buttonsBar.getHeight() : 0),
+                workingProtocol.getFbWidth(), workingProtocol.getFbHeight());
+	}
+
+	void registerResizeListener(Container container) {
+		container.addComponentListener(new ComponentAdapter() {
+			@Override
+			public void componentResized(ComponentEvent e) {
+				if (isZoomToFitSelected()) {
+					zoomToFit();
+					updateZoomButtonsState();
+					updateWindowTitle();
+					setSurfaceToHandleKbdFocus();
+				}
+			}
+		});
+	}
+
+	void updateZoomButtonsState() {
+		zoomOutButton.setEnabled(uiSettings.getScalePercent() > UiSettings.MIN_SCALE_PERCENT);
+		zoomInButton.setEnabled(uiSettings.getScalePercent() < UiSettings.MAX_SCALE_PERCENT);
+		zoomAsIsButton.setEnabled(uiSettings.getScalePercent() != 100);
+	}
+
+	public ButtonsBar createButtonsBar() {
+		buttonsBar = new ButtonsBar();
+		return buttonsBar;
+	}
+
+    public void setButtonsBarVisible(boolean isVisible) {
+        setButtonsBarVisible(isVisible, frame);
+    }
+
+	private void setButtonsBarVisible(boolean isVisible, Container container) {
+		buttonsBar.setVisible(isVisible);
+		if (isVisible) {
+			buttonsBar.borderOff();
+			container.add(buttonsBar.bar, BorderLayout.NORTH);
+            container.validate();
+		} else {
+			container.remove(buttonsBar.bar);
+			buttonsBar.borderOn();
+		}
+	}
+
+	public void setButtonsBarVisibleFS(boolean isVisible) {
+		if (isVisible) {
+			if ( ! buttonsBar.isVisible) {
+				lpane.add(buttonsBar.bar, JLayeredPane.POPUP_LAYER, 0);
+				final int bbWidth = buttonsBar.bar.getPreferredSize().width;
+				buttonsBar.bar.setBounds(
+						scroller.getViewport().getViewPosition().x + (scroller.getWidth() - bbWidth)/2, 0,
+						bbWidth, buttonsBar.bar.getPreferredSize().height);
+
+				// prevent mouse events to through down to Surface
+				if (null == buttonsBarMouseAdapter) buttonsBarMouseAdapter = new EmptyButtonsBarMouseAdapter();
+				buttonsBar.bar.addMouseListener(buttonsBarMouseAdapter);
+			}
+		} else {
+			buttonsBar.bar.removeMouseListener(buttonsBarMouseAdapter);
+			lpane.remove(buttonsBar.bar);
+			lpane.repaint(buttonsBar.bar.getBounds());
+		}
+		buttonsBar.setVisible(isVisible);
+	}
+
+    public Surface getSurface() {
+        return surface;
+    }
+
+    void close() {
+        if (isSeparateFrame && frame != null) {
+            frame.setVisible(false);
+            frame.dispose();
+        }
+    }
+
+    public static class ButtonsBar {
+		private static final Insets BUTTONS_MARGIN = new Insets(2, 2, 2, 2);
+		private JPanel bar;
+		private boolean isVisible;
+
+		public ButtonsBar() {
+			bar = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 1));
+		}
+
+		public JButton createButton(String iconId, String tooltipText, ActionListener actionListener) {
+			JButton button = new JButton(Utils.getButtonIcon(iconId));
+			button.setToolTipText(tooltipText);
+			button.setMargin(BUTTONS_MARGIN);
+			bar.add(button);
+			button.addActionListener(actionListener);
+			return button;
+		}
+
+		public void createStrut() {
+			bar.add(Box.createHorizontalStrut(10));
+		}
+
+		public JToggleButton createToggleButton(String iconId, String tooltipText, ItemListener itemListener) {
+			JToggleButton button = new JToggleButton(Utils.getButtonIcon(iconId));
+			button.setToolTipText(tooltipText);
+			button.setMargin(BUTTONS_MARGIN);
+			bar.add(button);
+			button.addItemListener(itemListener);
+			return button;
+		}
+
+		public void setVisible(boolean isVisible) {
+			this.isVisible = isVisible;
+            if (isVisible) bar.revalidate();
+		}
+
+		public int getWidth() {
+			return bar.getMinimumSize().width;
+		}
+		public int getHeight() {
+			return bar.getMinimumSize().height;
+		}
+
+		public void borderOn() {
+			bar.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
+		}
+
+		public void borderOff() {
+			bar.setBorder(BorderFactory.createEmptyBorder());
+		}
+	}
+
+	private static class EmptyButtonsBarMouseAdapter extends MouseAdapter {
+		// empty
+	}
+
+	private class FullscreenBorderDetectionThread extends Thread {
+		public static final int SHOW_HIDE_BUTTONS_BAR_DELAY_IN_MILLS = 700;
+		private final JFrame frame;
+		private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+		private ScheduledFuture<?> futureForShow;
+		private ScheduledFuture<?> futureForHide;
+		private Point mousePoint, oldMousePoint;
+		private Point viewPosition;
+
+		public FullscreenBorderDetectionThread(JFrame frame) {
+			super("FS border detector");
+			this.frame = frame;
+		}
+
+		public void run() {
+			setPriority(Thread.MIN_PRIORITY);
+			while(isFullScreen) {
+				mousePoint = MouseInfo.getPointerInfo().getLocation();
+				if (null == oldMousePoint) oldMousePoint = mousePoint;
+				SwingUtilities.convertPointFromScreen(mousePoint, frame);
+				viewPosition = scroller.getViewport().getViewPosition();
+				processButtonsBarVisibility();
+
+				boolean needScrolling = processVScroll() || processHScroll();
+				oldMousePoint = mousePoint;
+				if (needScrolling) {
+					cancelShowExecutor();
+					setButtonsBarVisibleFS(false);
+					makeScrolling(viewPosition);
+				}
+				try {
+                    Thread.sleep(100);
+                } catch (Exception e) {
+					// nop
+				}
+			}
+		}
+
+		private boolean processHScroll() {
+			if (mousePoint.x < FS_SCROLLING_ACTIVE_BORDER) {
+				if (viewPosition.x > 0) {
+					int delta = FS_SCROLLING_ACTIVE_BORDER - mousePoint.x;
+					if (mousePoint.y != oldMousePoint.y) delta *= 2; // speedify scrolling on mouse moving
+					viewPosition.x -= delta;
+					if (viewPosition.x < 0) viewPosition.x = 0;
+					return true;
+				}
+			} else if (mousePoint.x > (frame.getWidth() - FS_SCROLLING_ACTIVE_BORDER)) {
+				final Rectangle viewRect = scroller.getViewport().getViewRect();
+				final int right = viewRect.width + viewRect.x;
+				if (right < outerPanel.getSize().width) {
+					int delta = FS_SCROLLING_ACTIVE_BORDER - (frame.getWidth() - mousePoint.x);
+					if (mousePoint.y != oldMousePoint.y) delta *= 2; // speedify scrolling on mouse moving
+					viewPosition.x += delta;
+					if (viewPosition.x + viewRect.width > outerPanel.getSize().width) viewPosition.x =
+							outerPanel.getSize().width - viewRect.width;
+					return true;
+				}
+			}
+			return false;
+		}
+
+		private boolean processVScroll() {
+			if (mousePoint.y < FS_SCROLLING_ACTIVE_BORDER) {
+				if (viewPosition.y > 0) {
+					int delta = FS_SCROLLING_ACTIVE_BORDER - mousePoint.y;
+					if (mousePoint.x != oldMousePoint.x) delta *= 2; // speedify scrolling on mouse moving
+					viewPosition.y -= delta;
+					if (viewPosition.y < 0) viewPosition.y = 0;
+					return true;
+				}
+			} else if (mousePoint.y > (frame.getHeight() - FS_SCROLLING_ACTIVE_BORDER)) {
+				final Rectangle viewRect = scroller.getViewport().getViewRect();
+				final int bottom = viewRect.height + viewRect.y;
+				if (bottom < outerPanel.getSize().height) {
+					int delta = FS_SCROLLING_ACTIVE_BORDER - (frame.getHeight() - mousePoint.y);
+					if (mousePoint.x != oldMousePoint.x) delta *= 2; // speedify scrolling on mouse moving
+					viewPosition.y += delta;
+					if (viewPosition.y + viewRect.height > outerPanel.getSize().height) viewPosition.y =
+							outerPanel.getSize().height - viewRect.height;
+					return true;
+				}
+			}
+			return false;
+		}
+
+		private void processButtonsBarVisibility() {
+			if (mousePoint.y < 1) {
+				cancelHideExecutor();
+				// show buttons bar after delay
+				if (! buttonsBar.isVisible && (null == futureForShow || futureForShow.isDone())) {
+					futureForShow = scheduler.schedule(new Runnable() {
+						@Override
+						public void run() {
+							showButtonsBar();
+						}
+					}, SHOW_HIDE_BUTTONS_BAR_DELAY_IN_MILLS, TimeUnit.MILLISECONDS);
+				}
+			} else {
+				cancelShowExecutor();
+			}
+			if (buttonsBar.isVisible && mousePoint.y <= buttonsBar.getHeight()) {
+				cancelHideExecutor();
+			}
+			if (buttonsBar.isVisible && mousePoint.y > buttonsBar.getHeight()) {
+				// hide buttons bar after delay
+				if (null == futureForHide || futureForHide.isDone()) {
+					futureForHide = scheduler.schedule(new Runnable() {
+						@Override
+						public void run() {
+							SwingUtilities.invokeLater(new Runnable() {
+								@Override
+								public void run() {
+									setButtonsBarVisibleFS(false);
+									SwingViewerWindow.this.frame.validate();
+								}
+							});
+						}
+					}, SHOW_HIDE_BUTTONS_BAR_DELAY_IN_MILLS, TimeUnit.MILLISECONDS);
+				}
+			}
+		}
+
+		private void cancelHideExecutor() {
+			cancelExecutor(futureForHide);
+		}
+		private void cancelShowExecutor() {
+			cancelExecutor(futureForShow);
+		}
+
+		private void cancelExecutor(ScheduledFuture<?> future) {
+			if (future != null && ! future.isDone()) {
+				future.cancel(true);
+			}
+		}
+
+		private void makeScrolling(final Point viewPosition) {
+			SwingUtilities.invokeLater(new Runnable() {
+				@Override
+				public void run() {
+					scroller.getViewport().setViewPosition(viewPosition);
+					final Point mousePosition = surface.getMousePosition();
+					if (mousePosition != null) {
+						final MouseEvent mouseEvent = new MouseEvent(frame, 0, 0, 0,
+								mousePosition.x, mousePosition.y, 0, false);
+						for (MouseMotionListener mml : surface.getMouseMotionListeners()) {
+							mml.mouseMoved(mouseEvent);
+						}
+					}
+				}
+			});
+		}
+
+		private void showButtonsBar() {
+			SwingUtilities.invokeLater(new Runnable() {
+				@Override
+				public void run() {
+					setButtonsBarVisibleFS(true);
+				}
+			});
+		}
+	}
+
+    protected void createButtonsPanel(final ProtocolContext context, Container container) {
+        final SwingViewerWindow.ButtonsBar buttonsBar = createButtonsBar();
+
+        buttonsBar.createButton("options", "Set Options", new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                showOptionsDialog();
+                setSurfaceToHandleKbdFocus();
+            }
+        });
+
+        buttonsBar.createButton("info", "Show connection info", new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                showConnectionInfoMessage();
+                setSurfaceToHandleKbdFocus();
+            }
+        });
+
+        buttonsBar.createStrut();
+
+        buttonsBar.createButton("refresh", "Refresh screen", new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                context.sendRefreshMessage();
+                setSurfaceToHandleKbdFocus();
+            }
+        });
+
+        addZoomButtons();
+
+        kbdButtons = new LinkedList<JComponent>();
+
+        buttonsBar.createStrut();
+
+        JButton ctrlAltDelButton = buttonsBar.createButton("ctrl-alt-del", "Send 'Ctrl-Alt-Del'", new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                sendCtrlAltDel(context);
+                setSurfaceToHandleKbdFocus();
+            }
+        });
+        kbdButtons.add(ctrlAltDelButton);
+
+        JButton winButton = buttonsBar.createButton("win", "Send 'Win' key as 'Ctrl-Esc'", new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                sendWinKey(context);
+                setSurfaceToHandleKbdFocus();
+            }
+        });
+        kbdButtons.add(winButton);
+
+        JToggleButton ctrlButton = buttonsBar.createToggleButton("ctrl", "Ctrl Lock",
+                new ItemListener() {
+                    @Override
+                    public void itemStateChanged(ItemEvent e) {
+                        if (e.getStateChange() == ItemEvent.SELECTED) {
+                            context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true));
+                        } else {
+                            context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false));
+                        }
+                        setSurfaceToHandleKbdFocus();
+                    }
+                });
+        kbdButtons.add(ctrlButton);
+
+        JToggleButton altButton = buttonsBar.createToggleButton("alt", "Alt Lock",
+                new ItemListener() {
+                    @Override
+                    public void itemStateChanged(ItemEvent e) {
+                        if (e.getStateChange() == ItemEvent.SELECTED) {
+                            context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, true));
+                        } else {
+                            context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, false));
+                        }
+                        setSurfaceToHandleKbdFocus();
+                    }
+                });
+        kbdButtons.add(altButton);
+
+        ModifierButtonEventListener modifierButtonListener = new ModifierButtonEventListener();
+        modifierButtonListener.addButton(KeyEvent.VK_CONTROL, ctrlButton);
+        modifierButtonListener.addButton(KeyEvent.VK_ALT, altButton);
+        surface.addModifierListener(modifierButtonListener);
+
+//		JButton fileTransferButton = new JButton(Utils.getButtonIcon("file-transfer"));
+//		fileTransferButton.setMargin(buttonsMargin);
+//		buttonBar.add(fileTransferButton);
+
+        buttonsBar.createStrut();
+
+        buttonsBar.createButton("close", isApplet ? "Disconnect" : "Close", new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                if (frame != null) {
+                    frame.setVisible(false);
+                    frame.dispose();
+                }
+                presenter.setNeedReconnection(false);
+                presenter.cancelConnection();
+                viewer.closeApp();
+            }
+        }).setAlignmentX(JComponent.RIGHT_ALIGNMENT);
+
+        setButtonsBarVisible(true, container);
+    }
+
+    private void sendCtrlAltDel(ProtocolContext context) {
+        context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true));
+        context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, true));
+        context.sendMessage(new KeyEventMessage(Keymap.K_DELETE, true));
+        context.sendMessage(new KeyEventMessage(Keymap.K_DELETE, false));
+        context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, false));
+        context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false));
+    }
+
+    private void sendWinKey(ProtocolContext context) {
+        context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true));
+        context.sendMessage(new KeyEventMessage(Keymap.K_ESCAPE, true));
+        context.sendMessage(new KeyEventMessage(Keymap.K_ESCAPE, false));
+        context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false));
+    }
+
+    @Override
+    public void settingsChanged(SettingsChangedEvent e) {
+        if (ProtocolSettings.isRfbSettingsChangedFired(e)) {
+            ProtocolSettings settings = (ProtocolSettings) e.getSource();
+            setEnabledKbdButtons( ! settings.isViewOnly());
+        }
+    }
+
+    private void setEnabledKbdButtons(boolean enabled) {
+        if (kbdButtons != null) {
+            for (JComponent b : kbdButtons) {
+                b.setEnabled(enabled);
+            }
+        }
+    }
+
+    private void showOptionsDialog() {
+        OptionsDialog optionsDialog = new OptionsDialog(frame);
+        optionsDialog.initControlsFromSettings(rfbSettings, uiSettings, false);
+        optionsDialog.setVisible(true);
+        presenter.saveHistory();
+    }
+
+    private void showConnectionInfoMessage() {
+        StringBuilder message = new StringBuilder();
+        message.append("TightVNC Viewer v.").append(Viewer.ver()).append("\n\n");
+        message.append("Connected to: ").append(remoteDesktopName).append("\n");
+        message.append("Host: ").append(connectionString).append("\n\n");
+
+        message.append("Desktop geometry: ")
+                .append(String.valueOf(surface.getWidth()))
+                .append(" \u00D7 ") // multiplication sign
+                .append(String.valueOf(surface.getHeight())).append("\n");
+        message.append("Color format: ")
+                .append(String.valueOf(Math.round(Math.pow(2, workingProtocol.getPixelFormat().depth))))
+                .append(" colors (")
+                .append(String.valueOf(workingProtocol.getPixelFormat().depth))
+                .append(" bits)\n");
+        message.append("Current protocol version: ")
+                .append(workingProtocol.getProtocolVersion());
+        if (workingProtocol.isTight()) {
+            message.append("tight");
+        }
+        message.append("\n");
+
+        JOptionPane infoPane = new JOptionPane(message.toString(), JOptionPane.INFORMATION_MESSAGE);
+        final JDialog infoDialog = infoPane.createDialog(frame, "VNC connection info");
+        infoDialog.setModalityType(Dialog.ModalityType.MODELESS);
+        infoDialog.setVisible(true);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SwingViewerWindowFactory.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,37 @@
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.rfb.protocol.Protocol;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.viewer.ConnectionPresenter;
+import com.glavsoft.viewer.UiSettings;
+import com.glavsoft.viewer.Viewer;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class SwingViewerWindowFactory {
+
+    private final boolean isSeparateFrame;
+    private final boolean isApplet;
+    private final Viewer viewer;
+
+    public SwingViewerWindowFactory(boolean isSeparateFrame, boolean isApplet, Viewer viewer) {
+        this.isSeparateFrame = isSeparateFrame;
+        this.isApplet = isApplet;
+        this.viewer = viewer;
+    }
+
+    public SwingViewerWindow createViewerWindow(Protocol workingProtocol,
+                                                ProtocolSettings rfbSettings, UiSettings uiSettings,
+                                                String connectionString, ConnectionPresenter presenter) {
+        Surface surface = new Surface(workingProtocol, uiSettings.getScaleFactor(), uiSettings.getMouseCursorShape());
+        final SwingViewerWindow viewerWindow = new SwingViewerWindow(workingProtocol, rfbSettings, uiSettings,
+                surface, isSeparateFrame, isApplet, viewer, connectionString, presenter);
+        surface.setViewerWindow(viewerWindow);
+        viewerWindow.setRemoteDesktopName(workingProtocol.getRemoteDesktopName());
+        rfbSettings.addListener(viewerWindow);
+        uiSettings.addListener(surface);
+        return viewerWindow;
+    }
+
+}
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/Utils.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/Utils.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,13 +24,16 @@
 
 package com.glavsoft.viewer.swing;
 
-import java.awt.Image;
-import java.awt.Toolkit;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.ImageObserver;
 import java.net.URL;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-
-import javax.swing.ImageIcon;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Utils for Swing GUI
@@ -38,12 +41,7 @@
 public class Utils {
 	private static List<Image> icons;
 
-	/**
-	 * Get application icons
-	 *
-	 * @return icons list
-	 */
-	public static List<Image> getIcons() {
+	private static List<Image> getApplicationIcons() {
 		if (icons != null) {
 			return icons;
 		}
@@ -69,4 +67,70 @@
 		URL resource = Utils.class.getResource("/com/glavsoft/viewer/images/button-"+name+".png");
 		return resource != null ? new ImageIcon(resource) : null;
 	}
+
+    private static Map<LocalMouseCursorShape, Cursor> cursorCash = new HashMap<LocalMouseCursorShape, Cursor>();
+    public static Cursor getCursor(LocalMouseCursorShape cursorShape) {
+        Cursor cursor = cursorCash.get(cursorShape);
+        if (cursor != null) return cursor;
+        String name = cursorShape.getCursorName();
+        URL resource = Utils.class.getResource("/com/glavsoft/viewer/images/cursor-"+name+".png");
+        if (resource != null) {
+            Image image = Toolkit.getDefaultToolkit().getImage(resource);
+            if (image != null) {
+                final CountDownLatch done = new CountDownLatch(1);
+                image.getWidth(new ImageObserver() {
+                    @Override
+                    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
+                        boolean isReady = (infoflags & (ALLBITS | ABORT)) != 0;
+                        if (isReady) {
+                            done.countDown();
+                        }
+                        return ! isReady;
+                    }
+                });
+                try {
+                    done.await(3, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    return Cursor.getDefaultCursor();
+                }
+                int w = image.getWidth(null);
+                int h = image.getHeight(null);
+                if (w < 0 || h < 0) return Cursor.getDefaultCursor();
+                w = (int)((w-0.5) / 2);
+                h = (int)((h-0.5) / 2);
+                cursor = Toolkit.getDefaultToolkit().createCustomCursor(
+                        image, new Point(w > 0 ? w: 0, h > 0 ? h : 0), name);
+                if (cursor != null) cursorCash.put(cursorShape, cursor);
+            }
+        }
+        return cursor != null ? cursor : Cursor.getDefaultCursor();
+    }
+
+    public static void decorateDialog(Window dialog) {
+        try {
+            dialog.setAlwaysOnTop(true);
+        } catch (SecurityException e) {
+            // nop
+        }
+		dialog.pack();
+        if (dialog instanceof JDialog) {
+		    ((JDialog)dialog).setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
+        }
+        dialog.toFront();
+		Utils.setApplicationIconsForWindow(dialog);
+	}
+
+	public static void setApplicationIconsForWindow(Window window) {
+		List<Image> icons = getApplicationIcons();
+		if (icons.size() != 0) {
+			window.setIconImages(icons);
+		}
+	}
+
+	public static void centerWindow(Window window) {
+        Point locationPoint = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
+		Rectangle bounds = window.getBounds();
+		locationPoint.setLocation(locationPoint.x - bounds.width/2, locationPoint.y - bounds.height/2);
+		window.setLocation(locationPoint);
+	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/WrongParameterException.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,48 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing;
+
+import com.glavsoft.exceptions.CommonException;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class WrongParameterException extends CommonException {
+
+    private String propertyName;
+
+    public WrongParameterException(String message) {
+        super(message);
+    }
+
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    public WrongParameterException(String message, String propertyName) {
+        super(message);
+        this.propertyName = propertyName;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/AutoCompletionComboEditorDocument.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,112 @@
+package com.glavsoft.viewer.swing.gui;
+
+import javax.swing.*;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.PlainDocument;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+
+/**
+ * @author dime at tightvnc.com
+ *
+ * Using idea by Thomas Bierhance from http://www.orbital-computer.de/JComboBox/
+ */
+public class AutoCompletionComboEditorDocument extends PlainDocument {
+
+	private ComboBoxModel model;
+	private boolean selecting;
+	private JComboBox comboBox;
+	private final boolean hidePopupOnFocusLoss;
+	private JTextComponent editor;
+
+	public AutoCompletionComboEditorDocument(final JComboBox comboBox) {
+		this.comboBox = comboBox;
+		this.model = comboBox.getModel();
+		this.editor = (JTextComponent)comboBox.getEditor().getEditorComponent();
+		editor.setDocument(this);
+		comboBox.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				if (!selecting) highlightCompletedText(0);
+			}
+		});
+
+		Object selectedItem = comboBox.getSelectedItem();
+		if (selectedItem!=null) {
+			setText(selectedItem.toString());
+			highlightCompletedText(0);
+		}
+		hidePopupOnFocusLoss = System.getProperty("java.version").startsWith("1.5");
+		editor.addFocusListener(new FocusAdapter() {
+			@Override
+			public void focusLost(FocusEvent e) {
+				if (hidePopupOnFocusLoss) comboBox.setPopupVisible(false);
+			}
+		});
+	}
+
+	@Override
+	public void remove(int offs, int len) throws BadLocationException {
+		if (selecting) return;
+		super.remove(offs, len);
+	}
+
+	@Override
+	public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
+		if (selecting) return;
+		super.insertString(offs, str, a);
+		Object item = lookupItem(getText(0, getLength()));
+		if (item != null) {
+			setSelectedItem(item);
+			setText(item.toString());
+			highlightCompletedText(offs + str.length());
+			if (comboBox.isDisplayable()) comboBox.setPopupVisible(true);
+		}
+	}
+
+	private void setText(String text) {
+		try {
+			super.remove(0, getLength());
+			super.insertString(0, text, null);
+		} catch (BadLocationException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private void setSelectedItem(Object item) {
+		selecting = true;
+		model.setSelectedItem(item);
+		selecting = false;
+	}
+
+	private void highlightCompletedText(int offs) {
+		JTextComponent editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
+		editor.setSelectionStart(offs);
+		editor.setSelectionEnd(getLength());
+	}
+
+	private Object lookupItem(String pattern) {
+		Object selectedItem = model.getSelectedItem();
+		if (selectedItem != null && startsWithIgnoreCase(selectedItem, pattern)) {
+			return selectedItem;
+		} else {
+			for (int i = 0, n = model.getSize(); i < n; i++) {
+				Object currentItem = model.getElementAt(i);
+				if (startsWithIgnoreCase(currentItem, pattern)) {
+					return currentItem;
+				}
+			}
+		}
+		return null;
+	}
+
+	private boolean startsWithIgnoreCase(Object currentItem, String pattern) {
+		return currentItem.toString().toLowerCase().startsWith(pattern.toLowerCase());
+	}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/ConnectionView.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,471 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing.gui;
+
+import com.glavsoft.viewer.mvp.View;
+import com.glavsoft.viewer.swing.ConnectionParams;
+import com.glavsoft.viewer.ConnectionPresenter;
+import com.glavsoft.viewer.swing.Utils;
+import com.glavsoft.viewer.swing.WrongParameterException;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.LinkedList;
+
+/**
+ * Dialog window for connection parameters get from.
+ */
+@SuppressWarnings("serial")
+public class ConnectionView extends JPanel implements View {
+    private static final int PADDING = 4;
+    public static final int COLUMNS_HOST_FIELD = 30;
+    public static final int COLUMNS_PORT_USER_FIELD = 13;
+    public static final String CLOSE = "Close";
+    public static final String CANCEL = "Cancel";
+    private WindowListener appWindowListener;
+	private final boolean hasSshSupport;
+    private final JTextField serverPortField;
+	private JCheckBox useSshTunnelingCheckbox;
+	private final JComboBox serverNameCombo;
+    private JTextField sshUserField;
+    private JTextField sshHostField;
+    private JTextField sshPortField;
+    private JLabel sshUserLabel;
+    private JLabel sshHostLabel;
+    private JLabel sshPortLabel;
+    private JLabel ssUserWarningLabel;
+    private JButton clearHistoryButton;
+    private JButton connectButton;
+    private final JFrame view;
+    private final ConnectionPresenter presenter;
+    private final StatusBar statusBar;
+    private boolean connectionInProgress;
+    private JButton closeCancelButton;
+
+    public ConnectionView(final WindowListener appWindowListener,
+                          final ConnectionPresenter presenter, boolean useSsh) {
+        this.appWindowListener = appWindowListener;
+		this.hasSshSupport = useSsh;
+        this.presenter = presenter;
+
+        setLayout(new BorderLayout(0, 0));
+		JPanel optionsPane = new JPanel(new GridBagLayout());
+		add(optionsPane, BorderLayout.CENTER);
+		optionsPane.setBorder(new EmptyBorder(PADDING, PADDING, PADDING, PADDING));
+
+		setLayout(new GridBagLayout());
+
+		int gridRow = 0;
+
+        serverNameCombo = new JComboBox();
+        initConnectionsHistoryCombo();
+        serverNameCombo.addItemListener(new ItemListener() {
+            @Override
+            public void itemStateChanged(ItemEvent e) {
+                Object item = serverNameCombo.getSelectedItem();
+                if (item instanceof ConnectionParams) {
+                    presenter.populateFromHistoryItem((ConnectionParams) item);
+                }
+            }
+        });
+
+        addFormFieldRow(optionsPane, gridRow, new JLabel("Remote Host:"), serverNameCombo, true);
+        ++gridRow;
+
+        serverPortField = new JTextField(COLUMNS_PORT_USER_FIELD);
+
+        addFormFieldRow(optionsPane, gridRow, new JLabel("Port:"), serverPortField, false);
+        ++gridRow;
+
+        if (this.hasSshSupport) {
+			gridRow = createSshOptions(optionsPane, gridRow);
+		}
+
+        JPanel buttonPanel = createButtons();
+
+		GridBagConstraints cButtons = new GridBagConstraints();
+		cButtons.gridx = 0; cButtons.gridy = gridRow;
+		cButtons.weightx = 100; cButtons.weighty = 100;
+		cButtons.gridwidth = 2; cButtons.gridheight = 1;
+		optionsPane.add(buttonPanel, cButtons);
+
+        view = new JFrame("New TightVNC Connection");
+        view.add(this, BorderLayout.CENTER);
+        statusBar = new StatusBar();
+        view.add(statusBar, BorderLayout.SOUTH);
+
+        view.getRootPane().setDefaultButton(connectButton);
+        view.addWindowListener(appWindowListener);
+//        view.setResizable(false);
+        Utils.decorateDialog(view);
+		Utils.centerWindow(view);
+	}
+
+    private void initConnectionsHistoryCombo() {
+        serverNameCombo.setEditable(true);
+
+        new AutoCompletionComboEditorDocument(serverNameCombo); // use autocompletion feature for ComboBox
+        serverNameCombo.setRenderer(new HostnameComboboxRenderer());
+
+        ConnectionParams prototypeDisplayValue = new ConnectionParams();
+        prototypeDisplayValue.hostName = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXЧЧ";
+        serverNameCombo.setPrototypeDisplayValue(prototypeDisplayValue);
+    }
+
+    public void showReconnectDialog(final String title, final String message) {
+            JOptionPane reconnectPane = new JOptionPane(message + "\nTry another connection?",
+                    JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION);
+            final JDialog reconnectDialog = reconnectPane.createDialog(ConnectionView.this, title);
+            Utils.decorateDialog(reconnectDialog);
+            reconnectDialog.setVisible(true);
+            if (reconnectPane.getValue() == null ||
+                    (Integer)reconnectPane.getValue() == JOptionPane.NO_OPTION) {
+                presenter.setNeedReconnection(false);
+                closeView();
+                view.dispose();
+                closeApp();
+            } else {
+                // TODO return when allowInteractive, close window otherwise
+//                forceConnectionDialog = allowInteractive;
+            }
+    }
+
+    public void setConnectionInProgress(boolean enable) {
+        if (enable) {
+            connectionInProgress = true;
+            closeCancelButton.setText(CANCEL);
+            connectButton.setEnabled(false);
+        } else {
+            connectionInProgress = false;
+            closeCancelButton.setText(CLOSE);
+            connectButton.setEnabled(true);
+        }
+    }
+
+    private JPanel createButtons() {
+        JPanel buttonPanel = new JPanel();
+
+        closeCancelButton = new JButton(CLOSE);
+        closeCancelButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                if (connectionInProgress) {
+                    presenter.cancelConnection();
+                    setConnectionInProgress(false);
+                } else {
+                    closeView();
+                    closeApp();
+                }
+            }
+        });
+
+        connectButton = new JButton("Connect");
+        buttonPanel.add(connectButton);
+        connectButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                setMessage("");
+                Object item = serverNameCombo.getSelectedItem();
+                String hostName = item instanceof ConnectionParams ?
+                        ((ConnectionParams) item).hostName :
+                        (String) item;
+                try {
+                    setConnectionInProgress(true);
+                    presenter.submitConnection(hostName);
+                } catch (WrongParameterException wpe) {
+                    if (ConnectionPresenter.PROPERTY_HOST_NAME.equals(wpe.getPropertyName())) {
+                        serverNameCombo.requestFocusInWindow();
+                    }
+                    if (ConnectionPresenter.PROPERTY_RFB_PORT_NUMBER.equals(wpe.getPropertyName())) {
+                        serverPortField.requestFocusInWindow();
+                    }
+                    showConnectionErrorDialog(wpe.getMessage());
+                    setConnectionInProgress(false);
+                }
+            }
+        });
+
+        JButton optionsButton = new JButton("Options...");
+        buttonPanel.add(optionsButton);
+        optionsButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                OptionsDialog od = new OptionsDialog(view);
+                od.initControlsFromSettings(presenter.getRfbSettings(), presenter.getUiSettings(), true);
+                od.setVisible(true);
+                view.toFront();
+            }
+        });
+
+        clearHistoryButton = new JButton("Clear history");
+        clearHistoryButton.setToolTipText("Clear connections history");
+        buttonPanel.add(clearHistoryButton);
+        clearHistoryButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                presenter.clearHistory();
+                clearHistoryButton.setEnabled(false);
+                view.toFront();
+            }
+        });
+        buttonPanel.add(closeCancelButton);
+        return buttonPanel;
+    }
+
+	private int createSshOptions(JPanel pane, int gridRow) {
+		GridBagConstraints cUseSshTunnelLabel = new GridBagConstraints();
+		cUseSshTunnelLabel.gridx = 0; cUseSshTunnelLabel.gridy = gridRow;
+		cUseSshTunnelLabel.weightx = 100; cUseSshTunnelLabel.weighty = 100;
+		cUseSshTunnelLabel.gridwidth = 2; cUseSshTunnelLabel.gridheight = 1;
+		cUseSshTunnelLabel.anchor = GridBagConstraints.LINE_START;
+		cUseSshTunnelLabel.ipadx = PADDING;
+		cUseSshTunnelLabel.ipady = 10;
+		useSshTunnelingCheckbox = new JCheckBox("Use SSH tunneling");
+		pane.add(useSshTunnelingCheckbox, cUseSshTunnelLabel);
+		++gridRow;
+
+        sshHostLabel = new JLabel("SSH Server:");
+        sshHostField = new JTextField(COLUMNS_HOST_FIELD);
+        addFormFieldRow(pane, gridRow, sshHostLabel, sshHostField, true);
+        ++gridRow;
+
+        sshPortLabel = new JLabel("SSH Port:");
+        sshPortField = new JTextField(COLUMNS_PORT_USER_FIELD);
+        addFormFieldRow(pane, gridRow, sshPortLabel, sshPortField, false);
+        ++gridRow;
+
+        sshUserLabel = new JLabel("SSH User:");
+        sshUserField = new JTextField(COLUMNS_PORT_USER_FIELD);
+        JPanel sshUserFieldPane = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
+        sshUserFieldPane.add(sshUserField);
+        ssUserWarningLabel = new JLabel(" (will be asked if not specified)");
+        sshUserFieldPane.add(ssUserWarningLabel);
+        addFormFieldRow(pane, gridRow, sshUserLabel, sshUserFieldPane, false);
+        ++gridRow;
+
+        useSshTunnelingCheckbox.addItemListener(new ItemListener() {
+            @Override
+            public void itemStateChanged(ItemEvent e) {
+                final boolean useSsh = e.getStateChange() == ItemEvent.SELECTED;
+                setUseSsh(useSsh);
+                presenter.setUseSsh(useSsh);
+            }
+        });
+
+        return gridRow;
+	}
+
+    private void addFormFieldRow(JPanel pane, int gridRow, JLabel label, JComponent field, boolean fill) {
+        GridBagConstraints cLabel = new GridBagConstraints();
+        cLabel.gridx = 0; cLabel.gridy = gridRow;
+        cLabel.weightx = 0;
+        cLabel.weighty = 100;
+        cLabel.gridwidth = cLabel.gridheight = 1;
+        cLabel.anchor = GridBagConstraints.LINE_END;
+        cLabel.ipadx = PADDING;
+        cLabel.ipady = 10;
+        pane.add(label, cLabel);
+
+        GridBagConstraints cField = new GridBagConstraints();
+        cField.gridx = 1; cField.gridy = gridRow;
+        cField.weightx = 0; cField.weighty = 100;
+        cField.gridwidth = cField.gridheight = 1;
+        cField.anchor = GridBagConstraints.LINE_START;
+        if (fill) cField.fill = GridBagConstraints.HORIZONTAL;
+        pane.add(field, cField);
+    }
+
+    /*
+     * Implicit View interface
+     */
+    public void setMessage(String message) {
+        statusBar.setMessage(message);
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setPortNumber(int portNumber) {
+        serverPortField.setText(String.valueOf(portNumber));
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public String getPortNumber() {
+        return serverPortField.getText();
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setSshHostName(String sshHostName) {
+        if (hasSshSupport) {
+            sshHostField.setText(sshHostName);
+        }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public String getSshHostName() {
+        if (hasSshSupport) {
+            return sshHostField.getText();
+        } else { return ""; }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setSshPortNumber(int sshPortNumber) {
+        if (hasSshSupport) {
+            sshPortField.setText(String.valueOf(sshPortNumber));
+        }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public String getSshPortNumber() {
+        if (hasSshSupport) {
+            return sshPortField.getText();
+        } else { return ""; }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setSshUserName(String sshUserName) {
+        if (hasSshSupport) {
+            sshUserField.setText(sshUserName);
+        }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public String getSshUserName() {
+        if (hasSshSupport) {
+            return sshUserField.getText();
+        } else { return ""; }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setUseSsh(boolean useSsh) {
+        if (hasSshSupport) {
+            useSshTunnelingCheckbox.setSelected(useSsh);
+            sshUserLabel.setEnabled(useSsh);
+            sshUserField.setEnabled(useSsh);
+            ssUserWarningLabel.setEnabled(useSsh);
+            sshHostLabel.setEnabled(useSsh);
+            sshHostField.setEnabled(useSsh);
+            sshPortLabel.setEnabled(useSsh);
+            sshPortField.setEnabled(useSsh);
+        }
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public boolean getUseSsh() {
+        return useSshTunnelingCheckbox.isSelected();
+    }
+
+    @SuppressWarnings("UnusedDeclaration")
+    public void setConnectionsList(LinkedList<ConnectionParams> connections) {
+        serverNameCombo.removeAllItems();
+        for (ConnectionParams cp : connections) {
+            serverNameCombo.addItem(new ConnectionParams(cp));
+        }
+        serverNameCombo.setPopupVisible(false);
+        clearHistoryButton.setEnabled(serverNameCombo.getItemCount() > 0);
+    }
+    /*
+     * /Implicit View interface
+     */
+
+    @Override
+    public void showView() {
+        view.setVisible(true);
+        view.toFront();
+        view.repaint();
+    }
+
+    @Override
+    public void closeView() {
+        view.setVisible(false);
+    }
+
+    public void showConnectionErrorDialog(final String message) {
+        JOptionPane errorPane = new JOptionPane(message, JOptionPane.ERROR_MESSAGE);
+        final JDialog errorDialog = errorPane.createDialog(view, "Connection error");
+        Utils.decorateDialog(errorDialog);
+        errorDialog.setVisible(true);
+        if ( ! presenter.allowInteractive()) {
+            presenter.cancelConnection();
+            closeApp();
+        }
+    }
+
+    public void closeApp() {
+        appWindowListener.windowClosing(null);
+    }
+
+    public JFrame getFrame() {
+        return view;
+    }
+
+}
+
+class StatusBar extends JPanel {
+
+    private JLabel messageLabel;
+
+    public StatusBar() {
+        setLayout(new BorderLayout());
+        setPreferredSize(new Dimension(10, 23));
+
+        messageLabel = new JLabel("");
+        final Font f = messageLabel.getFont();
+        messageLabel.setFont(f.deriveFont(f.getStyle() & ~Font.BOLD));
+        add(messageLabel, BorderLayout.CENTER);
+
+        JPanel rightPanel = new JPanel(new BorderLayout());
+        rightPanel.setOpaque(false);
+
+
+        add(rightPanel, BorderLayout.EAST);
+        setBorder(new Border() {
+            @Override
+            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+                Color oldColor = g.getColor();
+                g.translate(x, y);
+                g.setColor(c.getBackground().darker());
+                g.drawLine(0, 0, width -1, 0);
+                g.setColor(c.getBackground().brighter());
+                g.drawLine(0, 1, width -1, 1);
+                g.translate(-x, -y);
+                g.setColor(oldColor);
+            }
+            @Override
+            public Insets getBorderInsets(Component c) {
+                return new Insets(2, 2, 2, 2);
+            }
+            @Override
+            public boolean isBorderOpaque() {
+                return false;
+            }
+        });
+    }
+
+    public void setMessage(String message) {
+        messageLabel.setText(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/ConnectionsHistory.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,338 @@
+package com.glavsoft.viewer.swing.gui;
+
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.mvp.Model;
+import com.glavsoft.viewer.swing.ConnectionParams;
+import com.glavsoft.viewer.UiSettings;
+import com.glavsoft.viewer.UiSettingsData;
+
+import java.io.*;
+import java.security.AccessControlException;
+import java.util.*;
+import java.util.logging.Logger;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class ConnectionsHistory implements Model {
+	private static int MAX_ITEMS = 32;
+    public static final String CONNECTIONS_HISTORY_ROOT_NODE = "com/glavsoft/viewer/connectionsHistory";
+    public static final String NODE_HOST_NAME = "hostName";
+    public static final String NODE_PORT_NUMBER = "portNumber";
+    public static final String NODE_SSH_USER_NAME = "sshUserName";
+    public static final String NODE_SSH_HOST_NAME = "sshHostName";
+    public static final String NODE_SSH_PORT_NUMBER = "sshPortNumber";
+    public static final String NODE_USE_SSH = "useSsh";
+    public static final String NODE_PROTOCOL_SETTINGS = "protocolSettings";
+    public static final String NODE_UI_SETTINGS = "uiSettings";
+    private final Logger logger;
+
+    private Map<ConnectionParams, ProtocolSettings> protocolSettingsMap;
+    private Map<ConnectionParams, UiSettingsData> uiSettingsDataMap;
+    LinkedList<ConnectionParams> connections;
+
+    public ConnectionsHistory() {
+        logger = Logger.getLogger(getClass().getName());
+        init();
+        retrieve();
+    }
+
+    private void init() {
+        protocolSettingsMap = new HashMap<ConnectionParams, ProtocolSettings>();
+        uiSettingsDataMap = new HashMap<ConnectionParams, UiSettingsData>();
+        connections = new LinkedList<ConnectionParams>();
+    }
+
+    private void retrieve() {
+        Preferences connectionsHistoryNode;
+        try {
+            connectionsHistoryNode = getConnectionHistoryNode();
+        } catch (AccessControlException ace) {
+            return;
+        }
+        try {
+            final String[] orderNums;
+			orderNums = connectionsHistoryNode.childrenNames();
+			SortedMap<Integer, ConnectionParams> conns = new TreeMap<Integer, ConnectionParams>();
+            HashSet<ConnectionParams> uniques = new HashSet<ConnectionParams>();
+			for (String orderNum : orderNums) {
+                int num = 0;
+                try {
+                    num = Integer.parseInt(orderNum);
+                } catch (NumberFormatException skip) {
+                    //nop
+                }
+				Preferences node = connectionsHistoryNode.node(orderNum);
+                String hostName = node.get(NODE_HOST_NAME, null);
+                if (null == hostName) continue; // skip entries without hostName field
+                ConnectionParams cp = new ConnectionParams(hostName, node.getInt(NODE_PORT_NUMBER, 0),
+                        node.getBoolean(NODE_USE_SSH, false), node.get(NODE_SSH_HOST_NAME, ""), node.getInt(NODE_SSH_PORT_NUMBER, 0), node.get(NODE_SSH_USER_NAME, "")
+                );
+                if (uniques.contains(cp)) continue; // skip duplicates
+                uniques.add(cp);
+                conns.put(num, cp);
+                logger.finest("deserialialize: " + cp.toPrint());
+                retrieveProtocolSettings(node, cp);
+                retrieveUiSettings(node, cp);
+			}
+			int itemsCount = 0;
+			for (ConnectionParams cp : conns.values()) {
+				if (itemsCount < MAX_ITEMS) {
+                    connections.add(cp);
+				} else {
+					connectionsHistoryNode.node(cp.hostName).removeNode();
+				}
+				++itemsCount;
+			}
+		} catch (BackingStoreException e) {
+            logger.severe("Cannot retrieve connections history info: " + e.getMessage());
+		}
+	}
+
+    private void retrieveUiSettings(Preferences node, ConnectionParams cp) {
+        byte[] bytes = node.getByteArray(NODE_UI_SETTINGS, new byte[0]);
+        if (bytes.length != 0) {
+            try {
+                UiSettingsData settings = (UiSettingsData) (new ObjectInputStream(
+                        new ByteArrayInputStream(bytes))).readObject();
+                uiSettingsDataMap.put(cp, settings);
+                logger.finest("deserialialize: " + settings);
+            } catch (IOException e) {
+                logger.info("Cannot deserialize UiSettings: " + e.getMessage());
+            } catch (ClassNotFoundException e) {
+                logger.severe("Cannot deserialize UiSettings : " + e.getMessage());
+            }
+        }
+    }
+
+    private void retrieveProtocolSettings(Preferences node, ConnectionParams cp) {
+        byte[] bytes = node.getByteArray(NODE_PROTOCOL_SETTINGS, new byte[0]);
+        if (bytes.length != 0) {
+            try {
+                ProtocolSettings settings = (ProtocolSettings) (new ObjectInputStream(
+                        new ByteArrayInputStream(bytes))).readObject();
+                settings.refine();
+                protocolSettingsMap.put(cp, settings);
+                logger.finest("deserialialize: " + settings);
+            } catch (IOException e) {
+                logger.info("Cannot deserialize ProtocolSettings: " + e.getMessage());
+            } catch (ClassNotFoundException e) {
+                logger.severe("Cannot deserialize ProtocolSettings : " + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Implicit Model interface
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public LinkedList<ConnectionParams> getConnectionsList() {
+		return connections;
+	}
+
+    public ProtocolSettings getProtocolSettings(ConnectionParams cp) {
+        return protocolSettingsMap.get(cp);
+    }
+
+    public UiSettingsData getUiSettingsData(ConnectionParams cp) {
+        return uiSettingsDataMap.get(cp);
+    }
+
+    public void save() {
+        try {
+            cleanStorage();
+            Preferences connectionsHistoryNode = getConnectionHistoryNode();
+            int num = 0;
+            for (ConnectionParams cp : connections) {
+                if (num >= MAX_ITEMS) break;
+                if ( ! Strings.isTrimmedEmpty(cp.hostName)) {
+                        addNode(cp, connectionsHistoryNode, num++);
+                }
+            }
+        } catch (AccessControlException ace) { /*nop*/ }
+	}
+
+
+    public void clear() {
+        cleanStorage();
+        init();
+    }
+
+    private void cleanStorage() {
+        Preferences connectionsHistoryNode = getConnectionHistoryNode();
+        try {
+            for (String host : connectionsHistoryNode.childrenNames()) {
+				connectionsHistoryNode.node(host).removeNode();
+			}
+		} catch (BackingStoreException e) {
+            logger.severe("Cannot remove node: " + e.getMessage());
+		}
+    }
+
+    private Preferences getConnectionHistoryNode() {
+        Preferences root = Preferences.userRoot();
+        return root.node(CONNECTIONS_HISTORY_ROOT_NODE);
+    }
+
+    private void addNode(ConnectionParams connectionParams, Preferences connectionsHistoryNode, int orderNum) {
+        ProtocolSettings protocolSettings = protocolSettingsMap.get(connectionParams);
+        UiSettingsData uiSettingsData = uiSettingsDataMap.get(connectionParams);
+        final Preferences node = connectionsHistoryNode.node(String.valueOf(orderNum));
+        serializeConnectionParams(node, connectionParams);
+        serializeProtocolSettings(node, protocolSettings);
+        serializeUiSettingsData(node, uiSettingsData);
+        try {
+			node.flush();
+		} catch (BackingStoreException e) {
+            logger.severe("Cannot retrieve connections history info: " + e.getMessage());
+		}
+	}
+
+    private void serializeUiSettingsData(Preferences node, UiSettingsData uiSettingsData) {
+        if (uiSettingsData != null) {
+            try {
+                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+                objectOutputStream.writeObject(uiSettingsData);
+                node.putByteArray(NODE_UI_SETTINGS, byteArrayOutputStream.toByteArray());
+                logger.finest("serialized (" + node.name() + ") " + uiSettingsData);
+            } catch (IOException e) {
+                logger.severe("Cannot serialize UiSettings: " + e.getMessage());
+            }
+        }
+    }
+
+    private void serializeProtocolSettings(Preferences node, ProtocolSettings protocolSettings) {
+        if (protocolSettings != null) {
+            try {
+                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+                objectOutputStream.writeObject(protocolSettings);
+                node.putByteArray(NODE_PROTOCOL_SETTINGS, byteArrayOutputStream.toByteArray());
+                logger.finest("serialized (" + node.name() + ") " + protocolSettings);
+            } catch (IOException e) {
+                logger.severe("Cannot serialize ProtocolSettings: " + e.getMessage());
+            }
+        }
+    }
+
+    private void serializeConnectionParams(Preferences node, ConnectionParams connectionParams) {
+        node.put(NODE_HOST_NAME, connectionParams.hostName);
+        node.putInt(NODE_PORT_NUMBER, connectionParams.getPortNumber());
+        if (connectionParams.useSsh()) {
+            node.putBoolean(NODE_USE_SSH, connectionParams.useSsh());
+            node.put(NODE_SSH_USER_NAME, connectionParams.sshUserName != null ? connectionParams.sshUserName: "");
+		    node.put(NODE_SSH_HOST_NAME, connectionParams.sshHostName != null ? connectionParams.sshHostName: "");
+		    node.putInt(NODE_SSH_PORT_NUMBER, connectionParams.getSshPortNumber());
+        }
+        logger.finest("serialized (" + node.name() + ") " + connectionParams.toPrint());
+    }
+
+    public void reorder(ConnectionParams connectionParams) {
+        reorder(connectionParams, getProtocolSettings(connectionParams), getUiSettingsData(connectionParams));
+    }
+
+    public void reorder(ConnectionParams connectionParams, ProtocolSettings protocolSettings, UiSettings uiSettings) {
+        reorder(connectionParams, protocolSettings, uiSettings != null? uiSettings.getData() : null);
+    }
+
+    private void reorder(ConnectionParams connectionParams, ProtocolSettings protocolSettings, UiSettingsData uiSettingsData) {
+        while (connections.remove(connectionParams)) {/*empty - remove all occurrences (if any)*/}
+		LinkedList<ConnectionParams> cpList = new LinkedList<ConnectionParams>();
+		cpList.addAll(connections);
+
+		connections.clear();
+		connections.add(new ConnectionParams(connectionParams));
+		connections.addAll(cpList);
+        storeSettings(connectionParams, protocolSettings, uiSettingsData);
+    }
+
+    private void storeSettings(ConnectionParams connectionParams, ProtocolSettings protocolSettings, UiSettingsData uiSettingsData) {
+        if (protocolSettings != null) {
+        ProtocolSettings savedSettings = protocolSettingsMap.get(connectionParams);
+            if (savedSettings != null) {
+                savedSettings.copyDataFrom(protocolSettings);
+            } else {
+                protocolSettingsMap.put(new ConnectionParams(connectionParams), new ProtocolSettings(protocolSettings));
+            }
+        }
+        if (uiSettingsData != null) {
+            uiSettingsDataMap.put(new ConnectionParams(connectionParams), new UiSettingsData(uiSettingsData));
+        }
+    }
+
+    /**
+     * Search most suitable connectionParams (cp) from history.
+     * When history is empty, returns original parameter.
+     * When original parameter is empty (null or hostName is empty) returns the very first cp from history.
+     * Then subsequently compare cp from history with original for most fields will match in sequent of
+     * hostName, portNumber, useSsh, sshHostName, sshPortName, sshPortNumber.
+     * When any match found it returns.
+     * When no matches found returns the very first cp from history.
+     *
+     * @param orig connectionParams to search
+     * @return most suitable cp
+     */
+    public ConnectionParams getMostSuitableConnection(ConnectionParams orig) {
+        ConnectionParams res = connections.isEmpty()? orig: connections.get(0);
+        if (null == orig || Strings.isTrimmedEmpty(orig.hostName)) return res;
+        for (ConnectionParams cp : connections) {
+            if (orig.equals(cp)) return cp;
+            if (compareTextFields(orig.hostName, res.hostName, cp.hostName)) {
+                res = cp;
+                continue;
+            }
+            if (orig.hostName.equals(cp.hostName) &&
+                    comparePorts(orig.getPortNumber(), res.getPortNumber(), cp.getPortNumber())) {
+                res = cp;
+                continue;
+            }
+            if (orig.hostName.equals(cp.hostName) &&
+                orig.getPortNumber() == cp.getPortNumber() &&
+                    orig.useSsh() == cp.useSsh() && orig.useSsh() != res.useSsh()) {
+                res = cp;
+                continue;
+            }
+            if (orig.hostName.equals(cp.hostName) &&
+                    orig.getPortNumber() == cp.getPortNumber() &&
+                    orig.useSsh() && cp.useSsh() &&
+                    compareTextFields(orig.sshHostName, res.sshHostName, cp.sshHostName)) {
+                res = cp;
+                continue;
+            }
+            if (orig.hostName.equals(cp.hostName) &&
+                    orig.getPortNumber() == cp.getPortNumber() &&
+                    orig.useSsh() && cp.useSsh() &&
+                    orig.sshHostName != null && orig.sshHostName.equals(cp.hostName) &&
+                    comparePorts(orig.getSshPortNumber(), res.getSshPortNumber(), cp.getSshPortNumber())) {
+                res = cp;
+                continue;
+            }
+            if (orig.hostName.equals(cp.hostName) &&
+                    orig.getPortNumber() == cp.getPortNumber() &&
+                    orig.useSsh() && cp.useSsh() &&
+                    orig.sshHostName != null && orig.sshHostName.equals(cp.hostName) &&
+                    orig.getSshPortNumber() == cp.getSshPortNumber() &&
+                    compareTextFields(orig.sshUserName, res.sshUserName, cp.sshUserName)) {
+                res = cp;
+            }
+        }
+        return res;
+    }
+
+    private boolean comparePorts(int orig, int res, int test) {
+        return orig == test && orig != res;
+    }
+
+    private boolean compareTextFields(String orig, String res, String test) {
+        return (orig != null && test != null && res != null) &&
+                orig.equals(test) && ! orig.equals(res);
+    }
+
+    public boolean isEmpty() {
+        return connections.isEmpty();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/HostnameComboboxRenderer.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,35 @@
+package com.glavsoft.viewer.swing.gui;
+
+import com.glavsoft.viewer.swing.ConnectionParams;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class HostnameComboboxRenderer extends DefaultListCellRenderer {
+
+    @Override
+    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+        String stringValue = renderListItem((ConnectionParams)value);
+        setText(stringValue);
+        setFont(getFont().deriveFont(Font.PLAIN));
+        if (isSelected) {
+            setBackground(list.getSelectionBackground());
+            setForeground(list.getSelectionForeground());
+        } else {
+            setBackground(list.getBackground());
+            setForeground(list.getForeground());
+        }
+        return this;
+    }
+
+    public String renderListItem(ConnectionParams cp) {
+        String s = "<html><b>" +cp.hostName + "</b>:" + cp.getPortNumber();
+        if (cp.useSsh()) {
+            s += " <i>(via ssh://" + cp.sshUserName + "@" + cp.sshHostName + ":" + cp.getSshPortNumber() + ")</i>";
+        }
+        return s + "</html>";
+    }
+}
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/OptionsDialog.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/OptionsDialog.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -27,6 +27,8 @@
 import com.glavsoft.rfb.encoding.EncodingType;
 import com.glavsoft.rfb.protocol.LocalPointer;
 import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.viewer.swing.LocalMouseCursorShape;
+import com.glavsoft.viewer.UiSettings;
 
 import javax.swing.*;
 import java.awt.*;
@@ -43,14 +45,11 @@
 	private JSlider compressionLevel;
 	private JCheckBox viewOnlyCheckBox;
 	private ProtocolSettings settings;
-	private JCheckBox sharedSession;
+    private UiSettings uiSettings;
+    private JCheckBox sharedSession;
 
 	private RadioButtonSelectedState<LocalPointer> mouseCursorTrackSelected;
 	private Map<LocalPointer, JRadioButton> mouseCursorTrackMap;
-
-//	private RadioButtonSelectedState<Integer> colorDepthSelected;
-//	private Map<Integer, JRadioButton> colorDepthMap;
-
 	private JCheckBox useCompressionLevel;
 	private JCheckBox useJpegQuality;
 	private JLabel jpegQualityPoorLabel;
@@ -61,9 +60,11 @@
 	private JComboBox encodings;
 	private JCheckBox disableClipboardTransfer;
 	private JComboBox colorDepth;
+    private RadioButtonSelectedState<LocalMouseCursorShape> mouseCursorShapeSelected;
+    private HashMap<LocalMouseCursorShape, JRadioButton> mouseCursorShapeMap;
 
-	public OptionsDialog(Window owner) {
-		super(owner, "Connection Options", ModalityType.MODELESS);
+    public OptionsDialog(Window owner) {
+		super(owner, "Connection Options", ModalityType.DOCUMENT_MODAL);
 		final WindowAdapter onClose = new WindowAdapter() {
 			@Override
 			public void windowClosing(WindowEvent e) {
@@ -83,25 +84,11 @@
 		pack();
 	}
 
-	public void initControlsFromSettings(ProtocolSettings settings, boolean isOnConnect) {
+	public void initControlsFromSettings(ProtocolSettings settings, UiSettings uiSettings, boolean isOnConnect) {
 		this.settings = settings;
-
-		viewOnlyCheckBox.setSelected(settings.isViewOnly());
+        this.uiSettings = uiSettings;
 
-//		String scaling = String.valueOf((int)(settings.scaling * 100));
-//		int i = 0; boolean isNotSetScale = true;
-//		while ( scaleCombo.getItemAt(i) != null) {
-//			String item = (String)scaleCombo.getItemAt(i);
-//			if (item.equals(scaling)) {
-//				scaleCombo.setSelectedIndex(i);
-//				isNotSetScale = false;
-//				break;
-//			}
-//			++i;
-//		}
-//		if (isNotSetScale) {
-//			scaleCombo.setSelectedItem(SCALE_100);
-//		}
+        viewOnlyCheckBox.setSelected(settings.isViewOnly());
 
 		int i = 0; boolean isNotSetEncoding = true;
 		while ( encodings.getItemAt(i) != null) {
@@ -122,19 +109,14 @@
 
 		mouseCursorTrackMap.get(settings.getMouseCursorTrack()).setSelected(true);
 		mouseCursorTrackSelected.setSelected(settings.getMouseCursorTrack());
+        mouseCursorShapeMap.get(uiSettings.getMouseCursorShape()).setSelected(true);
+        mouseCursorShapeSelected.setSelected(uiSettings.getMouseCursorShape());
 
-//		settings.setBitsPerPixel(settings.getBitsPerPixel()); // when 0 set default bpp value
-//		final JRadioButton colorDepthRadioButton = colorDepthMap.get(settings.getBitsPerPixel());
-//		if (colorDepthRadioButton != null) {
-//			colorDepthRadioButton.setSelected(true);
-//		}
-//		colorDepthSelected.setSelected(settings.getBitsPerPixel());
-
-		int bpp = settings.getBitsPerPixel();
+		int depth = settings.getColorDepth();
 		i = 0; boolean isNotSet = true;
 		while ( colorDepth.getItemAt(i) != null) {
-			int itemBpp = ((ColorDepthSelectItem)colorDepth.getItemAt(i)).bpp;
-			if (itemBpp == bpp) {
+			int itemDepth = ((ColorDepthSelectItem)colorDepth.getItemAt(i)).depth;
+			if (itemDepth == depth) {
 				colorDepth.setSelectedIndex(i);
 				isNotSet = false;
 				break;
@@ -163,8 +145,9 @@
 
 		settings.setSharedFlag(sharedSession.isSelected());
 		settings.setMouseCursorTrack(mouseCursorTrackSelected.getSelected());
+        uiSettings.setMouseCursorShape(mouseCursorShapeSelected.getSelected());
 
-		settings.setBitsPerPixel(((ColorDepthSelectItem)colorDepth.getSelectedItem()).bpp);
+		settings.setColorDepth(((ColorDepthSelectItem) colorDepth.getSelectedItem()).depth);
 
 		settings.setCompressionLevel(useCompressionLevel.isSelected() ?
 				compressionLevel.getValue() :
@@ -192,9 +175,8 @@
 		box.setAlignmentX(LEFT_ALIGNMENT);
 
 		box.add(createRestrictionsPanel());
-//		box.add(createDisplayPanel());
 		box.add(createMouseCursorPanel());
-//		box.add(createLocalShapePanel());
+		box.add(createLocalShapePanel());
 
 		sharedSession = new JCheckBox("Request shared session");
 		box.add(new JPanel(new FlowLayout(FlowLayout.LEFT)).add(sharedSession));
@@ -205,7 +187,6 @@
 
 	private JPanel createRestrictionsPanel() {
 		JPanel restrictionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-//		restrictionsPanel.setAlignmentX(LEFT_ALIGNMENT);
 		restrictionsPanel.setBorder(
 				BorderFactory.createTitledBorder(
 						BorderFactory.createEtchedBorder(), "Restrictions"));
@@ -272,10 +253,10 @@
 	}
 
 	private static class ColorDepthSelectItem {
-		final int bpp;
+		final int depth;
 		final String title;
-		public ColorDepthSelectItem(int bpp, String title) {
-			this.bpp = bpp;
+		public ColorDepthSelectItem(int depth, String title) {
+			this.depth = depth;
 			this.title = title;
 		}
 		@Override
@@ -290,32 +271,24 @@
 		colorDepthPanel.add(new JLabel("Color format: "));
 
 		colorDepth = new JComboBox();
-		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.BPP_SERVER_SETTINGS,
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_SERVER_SETTINGS,
 				"Server's default"));
-		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.BPP_32,
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_24,
 				"16 777 216 colors"));
-		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.BPP_16,
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_16,
 				"65 536 colors"));
-		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.BPP_8,
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_8,
 				"256 colors"));
-		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.BPP_6,
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_6,
 				"64 colors"));
-		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.BPP_3,
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_3,
 				"8 colors"));
 
 		colorDepthPanel.add(colorDepth);
 		colorDepth.addItemListener(new ItemListener() {
 			@Override
 			public void itemStateChanged(ItemEvent e) {
-				final ColorDepthSelectItem selectedItem =
-						(ColorDepthSelectItem) colorDepth.getSelectedItem();
-				setEnabled(selectedItem.bpp > ProtocolSettings.BPP_8 ||
-						selectedItem.bpp == ProtocolSettings.BPP_SERVER_SETTINGS,
-						useJpegQuality);
-				setEnabled(useJpegQuality.isSelected() &&
-						(selectedItem.bpp > ProtocolSettings.BPP_8 ||
-							selectedItem.bpp == ProtocolSettings.BPP_SERVER_SETTINGS),
-						jpegQuality, jpegQualityPoorLabel, jpegQualityBestLabel);
+                setJpegQualityPaneEnable();
 			}
 		});
 		return colorDepthPanel;
@@ -356,17 +329,20 @@
 
 	protected void setJpegQualityPaneEnable() {
 		if (useJpegQuality != null && colorDepth != null) {
-			int bpp = ((ColorDepthSelectItem)colorDepth.getSelectedItem()).bpp;
-			setEnabled(
-				bpp > ProtocolSettings.BPP_8 || bpp == ProtocolSettings.BPP_SERVER_SETTINGS,
-				useJpegQuality);
-			setEnabled(useJpegQuality.isSelected() &&
-				(bpp > ProtocolSettings.BPP_8 || bpp == ProtocolSettings.BPP_SERVER_SETTINGS),
+			int depth = ((ColorDepthSelectItem)colorDepth.getSelectedItem()).depth;
+			setEnabled(whetherJpegQualityPaneBeEnabled(depth), useJpegQuality);
+			setEnabled(useJpegQuality.isSelected() && whetherJpegQualityPaneBeEnabled(depth),
 					jpegQuality, jpegQualityPoorLabel, jpegQualityBestLabel);
 		}
 	}
 
-	private void addCompressionLevelPane(JPanel encodingsPanel) {
+    private boolean whetherJpegQualityPaneBeEnabled(int depth) {
+        return  ProtocolSettings.COLOR_DEPTH_16 == depth ||
+                ProtocolSettings.COLOR_DEPTH_24 == depth ||
+                ProtocolSettings.COLOR_DEPTH_SERVER_SETTINGS == depth;
+    }
+
+    private void addCompressionLevelPane(JPanel encodingsPanel) {
 		useCompressionLevel = new JCheckBox("Custom compression level:");
 		useCompressionLevel.setAlignmentX(LEFT_ALIGNMENT);
 		encodingsPanel.add(useCompressionLevel);
@@ -410,7 +386,6 @@
 		}
 	}
 
-	/*
 	private JPanel createLocalShapePanel() {
 		JPanel localCursorShapePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
 //		localCursorShapePanel.setLayout(new BoxLayout(localCursorShapePanel, BoxLayout.Y_AXIS));
@@ -420,23 +395,29 @@
 		Box localCursorShapeBox = Box.createVerticalBox();
 		localCursorShapePanel.add(localCursorShapeBox);
 
-		JRadioButton dotCursorRadio = new JRadioButton("Dot cursor");
-		JRadioButton smallDotCursorRadio = new JRadioButton("Small dot cursor");
-		JRadioButton arrowCursorRadio = new JRadioButton("Default cursor");
-		JRadioButton noCursorRadio = new JRadioButton("No local cursor");
-		localCursorShapeBox.add(dotCursorRadio);
-		localCursorShapeBox.add(smallDotCursorRadio);
-		localCursorShapeBox.add(arrowCursorRadio);
-		localCursorShapeBox.add(noCursorRadio);
-		ButtonGroup localCursorButtonGroup = new ButtonGroup();
-		localCursorButtonGroup.add(dotCursorRadio);
-		localCursorButtonGroup.add(smallDotCursorRadio);
-		localCursorButtonGroup.add(arrowCursorRadio);
-		localCursorButtonGroup.add(noCursorRadio);
+        ButtonGroup mouseCursorShapeTrackGroup = new ButtonGroup();
+        mouseCursorShapeSelected = new RadioButtonSelectedState<LocalMouseCursorShape>();
+        mouseCursorShapeMap = new HashMap<LocalMouseCursorShape, JRadioButton>();
+
+        addRadioButton("Dot cursor", LocalMouseCursorShape.DOT,
+                mouseCursorShapeSelected, mouseCursorShapeMap, localCursorShapeBox,
+                mouseCursorShapeTrackGroup);
+
+        addRadioButton("Small dot cursor", LocalMouseCursorShape.SMALL_DOT,
+                mouseCursorShapeSelected, mouseCursorShapeMap, localCursorShapeBox,
+                mouseCursorShapeTrackGroup);
+
+        addRadioButton("System default cursor", LocalMouseCursorShape.SYSTEM_DEFAULT,
+                mouseCursorShapeSelected, mouseCursorShapeMap, localCursorShapeBox,
+                mouseCursorShapeTrackGroup);
+
+        addRadioButton("No local cursor", LocalMouseCursorShape.NO_CURSOR,
+                mouseCursorShapeSelected, mouseCursorShapeMap, localCursorShapeBox,
+                mouseCursorShapeTrackGroup);
+
 		return localCursorShapePanel;
 	}
-*/
-	
+
 	private JPanel createMouseCursorPanel() {
 		JPanel mouseCursorPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
 		mouseCursorPanel.setBorder(
--- a/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/PasswordDialog.java	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/PasswordDialog.java	Wed Aug 07 19:01:17 2013 +0900
@@ -1,4 +1,4 @@
-// Copyright (C) 2010, 2011 GlavSoft LLC.
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
 // All rights reserved.
 //
 //-------------------------------------------------------------------------
@@ -24,6 +24,7 @@
 
 package com.glavsoft.viewer.swing.gui;
 
+import com.glavsoft.viewer.ConnectionWorker;
 import com.glavsoft.viewer.swing.Utils;
 
 import javax.swing.*;
@@ -31,8 +32,8 @@
 import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.WindowListener;
-import java.util.List;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
 
 /**
  * Dialog to ask password
@@ -44,17 +45,19 @@
 
 	private static final int PADDING = 4;
 	private final JLabel messageLabel;
-	public PasswordDialog(Frame owner, final WindowListener onClose, boolean isApplet) {
-		super(owner, "Login", isApplet ? ModalityType.APPLICATION_MODAL : ModalityType.TOOLKIT_MODAL);
-		try {
-			setAlwaysOnTop(true);
-		} catch (SecurityException e) { /*nop*/ }
-		addWindowListener(onClose);
+	public PasswordDialog(Frame owner, final ConnectionWorker onCancel) {
+		super(owner, "VNC Authentication", true);
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosed(WindowEvent windowEvent) {
+                onCancel.cancel();
+            }
+        });
 		JPanel pane = new JPanel(new GridLayout(0, 1, PADDING, PADDING));
 		add(pane);
 		pane.setBorder(new EmptyBorder(PADDING, PADDING, PADDING, PADDING));
 
-		messageLabel = new JLabel("Server requires password authentication");
+		messageLabel = new JLabel("Server requires VNC authentication");
 		pane.add(messageLabel);
 
 		JPanel passwordPanel = new JPanel();
@@ -74,36 +77,32 @@
 			}
 		});
 
-		JButton closeButton = new JButton("Close");
+		JButton closeButton = new JButton("Cancel");
 		buttonPanel.add(closeButton);
 		closeButton.addActionListener(new ActionListener() {
 			@Override
 			public void actionPerformed(ActionEvent e) {
+                password = null;
 				setVisible(false);
-				onClose.windowClosing(null);
+				onCancel.cancel();
 			}
 		});
 
 		pane.add(buttonPanel);
 
 		getRootPane().setDefaultButton(loginButton);
-
-		List<Image> icons = Utils.getIcons();
-		if (icons.size() != 0) {
-			setIconImages(icons);
-		}
-
-		// center dialog
-		Point locationPoint = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
-		pack();
-		Rectangle bounds = getBounds();
-		locationPoint.setLocation(
-				locationPoint.x - bounds.width/2, locationPoint.y - bounds.height/2);
-		setLocation(locationPoint);
+        Utils.decorateDialog(this);
+        Utils.centerWindow(this);
+        addWindowFocusListener(new WindowAdapter() {
+            @Override
+            public void windowGainedFocus(WindowEvent e) {
+                passwordField.requestFocusInWindow();
+            }
+        });
 	}
 
 	public void setServerHostName(String serverHostName) {
-		messageLabel.setText("Server '" + serverHostName + "' requires password authentication");
+		messageLabel.setText("Server '" + serverHostName + "' requires VNC authentication");
 		pack();
 	}
 
@@ -111,4 +110,6 @@
 		return password;
 	}
 
+
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshConnectionManager.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,167 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing.ssh;
+
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.CancelConnectionException;
+import com.glavsoft.viewer.swing.ConnectionParams;
+import com.glavsoft.viewer.swing.Utils;
+import com.jcraft.jsch.*;
+
+import javax.swing.*;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.logging.Logger;
+import java.util.prefs.Preferences;
+
+public class SshConnectionManager implements SshKnownHostsManager {
+
+    public static final String SSH_NODE = "com/glavsoft/viewer/ssh";
+    public static final String KNOWN_HOSTS = "known_hosts";
+    private Session session;
+    private String errorMessage = "";
+	private final JFrame parentWindow;
+    private JSch jsch;
+
+    public SshConnectionManager(JFrame parentWindow) {
+        this.parentWindow = parentWindow;
+	}
+
+	public int connect(ConnectionParams connectionParams) throws CancelConnectionException {
+        if (Strings.isTrimmedEmpty(connectionParams.sshUserName)) {
+            connectionParams.sshUserName = getInteractivelySshUserName();
+        }
+
+        if (session != null && session.isConnected()) {
+			session.disconnect();
+		}
+        jsch = new JSch();
+        try {
+            jsch.setKnownHosts(getKnownHostsStream());
+        } catch (JSchException e) {
+            Logger.getLogger(this.getClass().getName()).severe("Cannot set JSCH known hosts: " + e.getMessage());
+        }
+        int port = 0;
+		try {
+			session = jsch.getSession(
+                    connectionParams.sshUserName, connectionParams.sshHostName, connectionParams.getSshPortNumber());
+			UserInfo ui = new SwingSshUserInfo(parentWindow);
+			session.setUserInfo(ui);
+			session.connect();
+            sync();
+			port = session.setPortForwardingL(0, connectionParams.hostName, connectionParams.getPortNumber());
+        } catch (JSchException e) {
+            session.disconnect();
+			errorMessage = e.getMessage();
+		}
+		return port;
+	}
+
+    private String getInteractivelySshUserName() throws CancelConnectionException {
+        class Pair {
+            int intRes;
+            String stringRes;
+        }
+        final Pair result = new Pair();
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    final JOptionPane pane = new JOptionPane("Please enter the user name for SSH connection:",
+                            JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+                    pane.setWantsInput(true);
+                    final JDialog dialog = pane.createDialog(parentWindow, "SSH User Name");
+                    Utils.decorateDialog(dialog);
+                    dialog.setVisible(true);
+                    result.stringRes = pane.getInputValue() != null ? (String) pane.getInputValue() : "";
+                    result.intRes = pane.getValue() != null ? (Integer) pane.getValue() : JOptionPane.OK_CANCEL_OPTION;
+                    dialog.dispose();
+                }
+            });
+        } catch (InterruptedException e) {
+            Logger.getLogger(getClass().getName()).severe(e.getMessage());
+        } catch (InvocationTargetException e) {
+            Logger.getLogger(getClass().getName()).severe(e.getMessage());
+        }
+        if (result.intRes != JOptionPane.OK_OPTION) {
+            throw new CancelConnectionException("No Ssh User Name entered");
+        }
+        return result.stringRes;
+    }
+
+    public boolean isConnected() {
+		return session.isConnected();
+	}
+
+	public String getErrorMessage() {
+		return errorMessage;
+	}
+
+    private InputStream getKnownHostsStream() {
+        Preferences sshNode = Preferences.userRoot().node(SSH_NODE);
+        return new ByteArrayInputStream(sshNode.getByteArray(KNOWN_HOSTS, new byte[0]));
+    }
+
+    @Override
+    public void sync() {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HostKeyRepository repository = jsch.getHostKeyRepository();
+        try {
+            HostKey[] hostKey = repository.getHostKey();
+            if (null == hostKey) return;
+            for (HostKey hk : hostKey) {
+                String host = hk.getHost();
+                    String type = hk.getType();
+                    if (type.equals("UNKNOWN")) {
+                        write(out, host);
+                        write(out, "\n");
+                        continue;
+                    }
+                    write(out, host);
+                    write(out, " ");
+                    write(out, type);
+                    write(out, " ");
+                    write(out, hk.getKey());
+                    write(out, "\n");
+            }
+        } catch (IOException e) {
+            Logger.getLogger(this.getClass().getName()).severe("Cannot sync JSCH known hosts: " + e.getMessage());
+        }
+        Preferences sshNode = Preferences.userRoot().node(SSH_NODE);
+        sshNode.putByteArray(KNOWN_HOSTS, out.toByteArray());
+    }
+
+    private void write(ByteArrayOutputStream out, String str) throws IOException {
+        try {
+            out.write(str.getBytes("UTF-8"));
+        } catch (java.io.UnsupportedEncodingException e) {
+            out.write(str.getBytes());
+        }
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshKnownHostsManager.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,32 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing.ssh;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface SshKnownHostsManager {
+    void sync();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SwingSshUserInfo.java	Wed Aug 07 19:01:17 2013 +0900
@@ -0,0 +1,242 @@
+// Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC.
+// All rights reserved.
+//
+//-------------------------------------------------------------------------
+// This file is part of the TightVNC software.  Please visit our Web site:
+//
+//                       http://www.tightvnc.com/
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//-------------------------------------------------------------------------
+//
+
+package com.glavsoft.viewer.swing.ssh;
+
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.swing.Utils;
+import com.jcraft.jsch.UIKeyboardInteractive;
+import com.jcraft.jsch.UserInfo;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.util.logging.Logger;
+
+/**
+* @author dime at tightvnc.com
+*/
+class SwingSshUserInfo implements UserInfo, UIKeyboardInteractive {
+	private String password;
+    private String passphrase;
+	private final JFrame parentFrame;
+
+    SwingSshUserInfo(JFrame parentFrame) {
+		this.parentFrame = parentFrame;
+    }
+
+	@Override
+	public String getPassphrase() {
+		return passphrase;
+	}
+
+	@Override
+	public String getPassword() {
+		return password;
+	}
+
+	@Override
+	public boolean promptPassword(final String message) {
+        final int[] result = new int[1];
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    final JTextField passwordField = new JPasswordField(20);
+                    Object[] ob = {message, passwordField};
+                    JOptionPane pane = new JOptionPane(ob, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+                    final JDialog dialog = pane.createDialog(parentFrame, "SSH Authentication");
+                    Utils.decorateDialog(dialog);
+                    dialog.addWindowFocusListener(new WindowAdapter() {
+                        @Override
+                        public void windowGainedFocus(WindowEvent e) {
+                            passwordField.requestFocusInWindow();
+                        }
+                    });
+                    dialog.setVisible(true);
+                    result[0] = pane.getValue() != null ? (Integer)pane.getValue() : JOptionPane.CLOSED_OPTION;
+                    if (JOptionPane.OK_OPTION == result[0]) {
+                        password = passwordField.getText();
+                    }
+                    dialog.dispose();
+                }
+            });
+        } catch (InterruptedException e) {
+            getLogger().severe(e.getMessage());
+        } catch (InvocationTargetException e) {
+            getLogger().severe(e.getMessage());
+        }
+
+        return JOptionPane.OK_OPTION == result[0];
+	}
+
+	@Override
+	public boolean promptPassphrase(final String message) {
+        final int[] result = new int[1];
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    JTextField passphraseField = new JPasswordField(20);
+                    Object[] ob = {message, passphraseField};
+                    JOptionPane pane =
+                            new JOptionPane(ob, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+                    final JDialog dialog = pane.createDialog(parentFrame, "SSH Authentication");
+                    Utils.decorateDialog(dialog);
+                    dialog.setVisible(true);
+                    result[0] = pane.getValue() != null ? (Integer)pane.getValue() : JOptionPane.CLOSED_OPTION;
+                    if (JOptionPane.OK_OPTION == result[0]) {
+                        passphrase = passphraseField.getText();
+                    }
+                    dialog.dispose();
+                }
+            });
+        }  catch (InterruptedException e) {
+            getLogger().severe(e.getMessage());
+        } catch (InvocationTargetException e) {
+            getLogger().severe(e.getMessage());
+        }
+        return JOptionPane.OK_OPTION == result[0];
+	}
+
+	@Override
+	public boolean promptYesNo(final String message) {
+		final int[] result = new int[1];
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    JOptionPane pane = new JOptionPane(message, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION);
+                    final JDialog dialog = pane.createDialog(parentFrame, "SSH: Warning");
+                    Utils.decorateDialog(dialog);
+                    dialog.setVisible(true);
+                    result[0] = pane.getValue() != null ? (Integer)pane.getValue() : JOptionPane.CLOSED_OPTION;
+                    dialog.dispose();
+                }
+            });
+        }  catch (InterruptedException e) {
+            getLogger().severe(e.getMessage());
+        } catch (InvocationTargetException e) {
+            getLogger().severe(e.getMessage());
+        }
+        return JOptionPane.YES_OPTION == result[0];
+	}
+
+	@Override
+	public void showMessage(final String message) {
+		try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    JOptionPane pane = new JOptionPane(message, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION);
+                    final JDialog dialog = pane.createDialog(parentFrame, "SSH");
+                    Utils.decorateDialog(dialog);
+                    dialog.setVisible(true);
+                    dialog.dispose();
+                }
+            });
+        }  catch (InterruptedException e) {
+            getLogger().severe(e.getMessage());
+        } catch (InvocationTargetException e) {
+            getLogger().severe(e.getMessage());
+        }
+	}
+
+	@Override
+	public String[] promptKeyboardInteractive(final String destination,
+			final String name,
+			final String instruction,
+			final String[] prompt,
+			final boolean[] echo) {
+        class WrapRes {
+            String[] stringsRes;
+        }
+        final WrapRes wrapRes = new WrapRes();
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    Container panel = new JPanel();
+                    panel.setLayout(new GridBagLayout());
+
+                    final GridBagConstraints gbc =
+                            new GridBagConstraints(0, 0, 1, 1, 1, 1,
+                                    GridBagConstraints.NORTHWEST,
+                                    GridBagConstraints.NONE,
+                                    new Insets(0, 0, 0, 0), 0, 0);
+                    gbc.weightx = 1.0;
+                    gbc.gridwidth = GridBagConstraints.REMAINDER;
+                    gbc.gridx = 0;
+                    panel.add(new JLabel(instruction), gbc);
+                    gbc.gridy++;
+                    gbc.gridwidth = GridBagConstraints.RELATIVE;
+                    JTextField[] texts = new JTextField[prompt.length];
+                    for (int i = 0; i < prompt.length; i++) {
+                        gbc.fill = GridBagConstraints.NONE;
+                        gbc.gridx = 0;
+                        gbc.weightx = 1;
+                        panel.add(new JLabel(prompt[i]), gbc);
+                        gbc.gridx = 1;
+                        gbc.fill = GridBagConstraints.HORIZONTAL;
+                        gbc.weighty = 1;
+                        if (echo[i]) {
+                            texts[i] = new JTextField(20);
+                        } else {
+                            texts[i] = new JPasswordField(20);
+                        }
+                        panel.add(texts[i], gbc);
+                        gbc.gridy++;
+                    }
+
+                    final String title = "SSH authentication for " + destination + (Strings.isTrimmedEmpty(name) ? "" : (": " + name));
+
+                    final JOptionPane pane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+                    final JDialog dialog = pane.createDialog(parentFrame, title);
+                    Utils.decorateDialog(dialog);
+                    dialog.setVisible(true);
+                    int result = pane.getValue() != null ? (Integer)pane.getValue() : JOptionPane.CLOSED_OPTION;
+                    wrapRes.stringsRes = null;
+                    if (JOptionPane.OK_OPTION == result) {
+                        wrapRes.stringsRes = new String[prompt.length];
+                        for (int i = 0; i < prompt.length; i++) {
+                            wrapRes.stringsRes[i] = texts[i].getText();
+                        }
+                    }
+                    dialog.dispose();
+                }
+            });
+        } catch (InterruptedException e) {
+            getLogger().severe(e.getMessage());
+        } catch (InvocationTargetException e) {
+            getLogger().severe(e.getMessage());
+        }
+		return wrapRes.stringsRes;
+	}
+
+    private Logger getLogger() {
+        return Logger.getLogger(getClass().getName());
+    }
+}
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fit.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fullscreen.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-dot.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-nocursor.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-smalldot.png has changed
--- a/src/web/viewer-applet-example.html	Tue Jul 03 13:20:49 2012 +0900
+++ b/src/web/viewer-applet-example.html	Wed Aug 07 19:01:17 2013 +0900
@@ -1,13 +1,31 @@
-<!-- Copyright (C) 2010, 2011, 2012 GlavSoft LLC. -->
+<!-- Copyright (C) 2010, 2011, 2012, 2013 GlavSoft LLC. -->
 <!-- All rights reserved. -->
-<!-- -->
-<!-- DO NOT USE OR REDISTRIBUTE THE CODE, UNLESS YOU HAVE OBTAINED A LICENSE. -->
+<!--  -->
+<!-- ------------------------------------------------------------------------- -->
+<!-- This file is part of the TightVNC software.  Please visit our Web site: -->
+<!--  -->
+<!-- http://www.tightvnc.com/ -->
+<!--  -->
+<!-- This program is free software; you can redistribute it and/or modify -->
+<!-- it under the terms of the GNU General Public License as published by -->
+<!-- the Free Software Foundation; either version 2 of the License, or -->
+<!-- (at your option) any later version. -->
+<!--  -->
+<!-- This program is distributed in the hope that it will be useful, -->
+<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of -->
+<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -->
+<!-- GNU General Public License for more details. -->
+<!--  -->
+<!-- You should have received a copy of the GNU General Public License along -->
+<!-- with this program; if not, write to the Free Software Foundation, Inc., -->
+<!-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -->
+<!-- ------------------------------------------------------------------------- -->
 <!--  -->
 
 <html>
 	<title>TightVNC desktop</title>
 	<body>
-		<applet archive="${archive_name}.jar"
+		<applet archive="tightvnc-jviewer.jar"
 			code="com.glavsoft.viewer.Viewer"
 			width="1" height="1">
 			<param name="Host" value="localhost" /> <!-- Host to connect. Default:  the host from which the applet was loaded. -->
@@ -29,7 +47,13 @@
 
 			<param name="colorDepth" value="" /> <!-- Reserved for future. Possible values: 6, 8, 16, 24, 32 (equals to 24). Only 24/32 is supported now -->
 			<param name="ScalingFactor" value="100" /> <!-- Scale local representation of the remote desktop on startup. Default is 100 means 100% -->
-		</applet>
+            <!--param name="AllowAppletInteractiveConnections" value="no" /--> <!-- Allow applet interactively connect to other hosts then in HostName param or hostbase. Possible values: yes/true, no/false. Default: false. -->
+            <!-- SSH tunneling options -->
+            <param name="sshHost" value="" /><!-- SSH host name. -->
+            <param name="sshUser" value="" /><!-- SSH port number. When empty, standard SSH port number (22) is used -->
+            <param name="sshPort" value="" /><!-- SSH user name. -->
+
+        </applet>
 		<br />
 		<a href="http://www.tightvnc.com/">TightVNC Web Site</a>
 	</body>