changeset 0:daa24f8a557b

TightVNC original
author YU
date Thu, 11 Sep 2014 07:30:03 +0900
parents
children 7bee64adbbfd
files LICENSE.txt 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-alt.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-close.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl-alt-del.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-file-transfer.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-info.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-new-connection.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-options.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-rec.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-refresh.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-save.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-win.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-100.png 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/button-zoom-in.png src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-out.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/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-16x16.png src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-32x32.png src/web/viewer-applet-example.html
diffstat 134 files changed, 14241 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE.txt	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.txt	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,52 @@
+
+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
+as published by the Free Software Foundation. Please see the file
+LICENCE.txt for the exact licensing terms and conditions.
+
+The license does not permit incorporating this software into
+proprietary programs. If you wish to do so, please order commercial
+source code license. See the details here:
+
+  http://www.tightvnc.com/licensing/
+
+
+Using TightVNC Java Viewer
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+TightVNC Java Viewer can work either as a normal application or as
+an applet. To run it as an application, just execute the JAR file
+(tightvnc-jviewer.jar), e.g. double-click it in the Windows Explorer.
+If Java Runtime is installed on your computer correctly, that should
+be enough. You will be prompted for the TightVNC Server to connect to.
+
+If you would like to start the viewer from the command line, here are
+a few examples:
+
+  java -jar tightvnc-jviewer.jar
+  java -jar tightvnc-jviewer.jar hostname
+  java -jar tightvnc-jviewer.jar -port=nnn hostname
+  java -jar tightvnc-jviewer.jar -port=nnn -host=hostname
+
+... where hostname and nnn should be replaced with the actual hostname
+and port number correspondingly. Note that you can use an IP address
+as a hostname. Port 5900 will be used if not specified.
+
+Important: the syntax like hostname:display or hostname::port is not
+           supported in this version of TightVNC Java Viewer.
+
+For more command line params info run:
+
+  java -jar tightvnc-jviewer.jar -help
+
+Finally, if you would like to use the viewer as an applet, please see
+the example HTML page included (viewer-applet-example.html).
+
+======================================================================
+
+Thank you for using TightVNC!
+
+Visit our Web site:    http://www.tightvnc.com/
+Follow us on Twitter:  http://www.twitter.com/tighvnc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build.gradle	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,129 @@
+apply plugin:'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+version = '2.7.2'
+
+project.ext.baseName = 'tightvnc-jviewer'
+def buildNo = processBuildNo(version)
+
+defaultTasks 'clean', 'dist'
+
+configurations {
+    viewerSwingCompile { extendsFrom compile }
+    viewerSwingRuntime { extendsFrom viewerSwingCompile, runtime }
+}
+
+sourceSets {
+	viewerSwing {
+		java {
+			srcDir 'src/viewer_swing/java'
+		}
+		resources {
+			srcDir 'src/viewer_swing/resources'
+		}
+	}
+	main {
+		java.srcDirs += viewerSwing.java.srcDirs
+		resources.srcDirs += viewerSwing.resources.srcDirs
+	}
+}
+
+repositories {
+    flatDir {
+        dirs 'src/libs/'
+    }
+}
+
+dependencies {
+    compile group: 'com.jcraft', name: 'jsch', version: '0.1.+', ext: 'jar'
+    viewerSwingCompile group: 'com.jcraft', name: 'jsch', version: '0.1.+', ext: 'jar'
+    viewerSwingRuntime configurations.viewerSwingCompile
+}
+
+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
+    manifest {
+        attributes manifestAttributes
+    }
+    def runtimeDeps = configurations.viewerSwingRuntime.collect {
+        it.isDirectory() ? it : zipTree(it)
+    }
+    from(runtimeDeps) {
+        exclude 'META-INF/**'
+    }
+}
+
+task noSshJar (type: Jar, dependsOn: classes) {
+    baseName = 'nossh/' + project.baseName
+    version = null
+    manifest {
+        attributes manifestAttributes
+    }
+    from sourceSets.main.output
+}
+
+artifacts {
+    archives file('src/web/viewer-applet-example.html')
+    archives noSshJar
+}
+
+uploadArchives {
+    repositories {
+        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+            addArtifactPattern("$projectDir/dist/${project.baseName}-${project.version}/[artifact].[ext]")
+        }
+    }
+	uploadDescriptor = false
+}
+
+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]
+}
Binary file src/libs/jsch-0.1.50.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/core/SettingsChangedEvent.java	Thu Sep 11 07:30:03 2014 +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.core;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class SettingsChangedEvent {
+	private final Object source;
+
+	public SettingsChangedEvent(Object source) {
+		this.source = source;
+	}
+
+	public Object getSource() {
+		return source;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/drawing/ColorDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,140 @@
+// 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.drawing;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.transport.Reader;
+
+public class ColorDecoder {
+    protected byte redShift;
+	protected byte greenShift;
+	protected byte blueShift;
+	public short redMax;
+	public short greenMax;
+	public short blueMax;
+	public final int bytesPerPixel;
+	public final int bytesPerCPixel;
+    public final int bytesPerPixelTight;
+	private final byte[] buff;
+
+	private int startShift;
+	private int startShiftCompact;
+	private int addShiftItem;
+	private final boolean isTightSpecific;
+
+    public ColorDecoder(PixelFormat pf) {
+		redShift = pf.redShift;
+		greenShift = pf.greenShift;
+		blueShift = pf.blueShift;
+		redMax = pf.redMax;
+		greenMax = pf.greenMax;
+		blueMax = pf.blueMax;
+		bytesPerPixel = pf.bitsPerPixel / 8;
+        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;
+			startShiftCompact = 0;
+			addShiftItem = 8;
+		} else {
+			startShift = pf.bitsPerPixel - 8;
+			startShiftCompact = Math.max(0, pf.depth - 8);
+			addShiftItem = -8;
+		}
+		isTightSpecific = 4==bytesPerPixel && 3==bytesPerPixelTight &&
+				255 == redMax && 255 == greenMax && 255 == blueMax;
+	}
+
+	protected int readColor(Reader reader) throws TransportException {
+		return getColor(reader.readBytes(buff, 0, bytesPerPixel), 0);
+	}
+
+	protected int readCompactColor(Reader reader) throws TransportException {
+		return getCompactColor(reader.readBytes(buff, 0, bytesPerCPixel), 0);
+	}
+
+	protected int readTightColor(Reader reader) throws TransportException {
+		return getTightColor(reader.readBytes(buff, 0, bytesPerPixelTight), 0);
+	}
+
+	protected int convertColor(int rawColor) {
+		return  255 * (rawColor >> redShift & redMax) / redMax << 16 |
+				255 * (rawColor >> greenShift & greenMax) / greenMax << 8 |
+				255 * (rawColor >> blueShift & blueMax) / blueMax;
+	}
+
+	public void fillRawComponents(byte[] comp, byte[] bytes, int offset) {
+		int rawColor = getRawTightColor(bytes, offset);
+		comp[0] = (byte) (rawColor >> redShift & redMax);
+		comp[1] = (byte) (rawColor >> greenShift & greenMax);
+		comp[2] = (byte) (rawColor >> blueShift & blueMax);
+	}
+
+	public int getTightColor(byte[] bytes, int offset) {
+		return convertColor(getRawTightColor(bytes, offset));
+	}
+
+	private int getRawTightColor(byte[] bytes, int offset) {
+		if (isTightSpecific)
+			return (bytes[offset++] & 0xff)<<16 |
+					(bytes[offset++] & 0xff)<<8 |
+					bytes[offset] & 0xff;
+		else
+			return getRawColor(bytes, offset);
+	}
+
+	protected int getColor(byte[] bytes, int offset) {
+		return convertColor(getRawColor(bytes, offset));
+	}
+
+	private int getRawColor(byte[] bytes, int offset) {
+		int shift = startShift;
+		int item = addShiftItem;
+		int rawColor = (bytes[offset++] & 0xff)<<shift;
+		for (int i=1; i<bytesPerPixel; ++i) {
+			rawColor |= (bytes[offset++] & 0xff)<<(shift+=item);
+		}
+		return rawColor;
+	}
+
+	protected int getCompactColor(byte[] bytes, int offset) {
+		int shift = startShiftCompact;
+		int item = addShiftItem;
+		int rawColor = (bytes[offset++] & 0xff)<<shift;
+		for (int i=1; i< bytesPerCPixel; ++i) {
+			rawColor |= (bytes[offset++] & 0xff)<<(shift+=item);
+		}
+		return convertColor(rawColor);
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/drawing/Renderer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,347 @@
+// 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.drawing;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
+import com.glavsoft.transport.Reader;
+
+import java.util.Arrays;
+
+/**
+ * Render bitmap data
+ *
+ * @author dime @ tightvnc.com
+ */
+public abstract class Renderer {
+
+    protected Reader reader;
+    private final Object lock = new Object();
+
+    public abstract void drawJpegImage(byte[] bytes, int offset,
+                                       int jpegBufferLength, FramebufferUpdateRectangle rect);
+
+    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);
+    }
+
+    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 += colorDecoder.bytesPerPixel;
+            }
+        }
+    }
+
+    /**
+     * 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 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 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 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;
+            }
+        }
+    }
+
+    /**
+     * 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];
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 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 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 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);
+    }
+
+    public int readTightPixelColor(Reader reader) throws TransportException {
+        return colorDecoder.readTightColor(reader);
+    }
+
+    public ColorDecoder getColorDecoder() {
+        return colorDecoder;
+    }
+
+    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 getBytesPerPixel() {
+        return colorDecoder.bytesPerPixel;
+    }
+
+    public int getBytesPerCPixel() {
+        return colorDecoder.bytesPerCPixel;
+    }
+
+    public int getBytesPerPixelTight() {
+        return colorDecoder.bytesPerPixelTight;
+    }
+
+    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;
+    }
+
+    /**
+     * 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 new cursor position
+     *
+     * @param rect cursor position
+     */
+    public void decodeCursorPosition(FramebufferUpdateRectangle rect) {
+        synchronized (cursor.getLock()) {
+            cursor.updatePosition(rect.x, rect.y);
+        }
+    }
+
+    public Object getLock() {
+        return lock;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/drawing/SoftCursor.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,92 @@
+// 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.drawing;
+
+/**
+ * Abstract class for operations with soft cursor positions, dimensions and
+ * hot point position.
+ */
+public abstract class SoftCursor {
+
+	protected int hotX, hotY;
+	private int x, y;
+	public int width, height;
+	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) {
+		this.hotX = hotX;
+		this.hotY = hotY;
+		oldWidth = this.width = width;
+		oldHeight = this.height = height;
+		oldRX = rX = 0;
+		oldRY = rY = 0;
+	}
+
+	/**
+	 * Update cursor position
+	 *
+	 * @param newX
+	 * @param newY
+	 */
+	public void updatePosition(int newX, int newY) {
+		oldRX = rX; oldRY = rY;
+		oldWidth = width; oldHeight = height;
+		x = newX; y = newY;
+		rX = x - hotX; rY = y - hotY;
+	}
+
+	/**
+	 * Set new cursor dimensions and hot point position
+	 *
+	 * @param hotX
+	 * @param hotY
+	 * @param width
+	 * @param height
+	 */
+	public void setNewDimensions(int hotX, int hotY, int width,	int height) {
+		this.hotX = hotX;
+		this.hotY = hotY;
+		oldWidth = this.width;
+		oldHeight = this.height;
+		oldRX = rX; oldRY = rY;
+		rX = x - hotX; rY = y - hotY;
+		this.width = width;
+		this.height = height;
+	}
+
+	public void createCursor(int[] cursorPixels, int hotX, int hotY, int width, int height) {
+		createNewCursorImage(cursorPixels, hotX, hotY, width, height);
+		setNewDimensions(hotX, hotY, width, height);
+	}
+
+	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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/AuthenticationFailedException.java	Thu Sep 11 07:30:03 2014 +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.exceptions;
+
+/**
+ * Throws when authentication was wrong
+ */
+@SuppressWarnings("serial")
+public class AuthenticationFailedException extends ProtocolException {
+
+	private String reason;
+
+	public AuthenticationFailedException(String message) {
+		super(message);
+	}
+	public AuthenticationFailedException(String message, String reason) {
+		super(message);
+		this.reason = reason;
+	}
+	public String getReason() {
+		return reason;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/ClosedConnectionException.java	Thu Sep 11 07:30:03 2014 +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.exceptions;
+
+/**
+ * Throwed when connection closed (EOF)
+ */
+@SuppressWarnings("serial")
+public class ClosedConnectionException extends TransportException {
+
+	public ClosedConnectionException(Throwable exception) {
+		super(exception);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/CommonException.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,40 @@
+// 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.exceptions;
+
+@SuppressWarnings("serial")
+public class CommonException extends Exception {
+
+	public CommonException(Throwable exception) {
+		super(exception);
+	}
+	public CommonException(String message, Throwable exception) {
+		super(message, exception);
+	}
+	public CommonException(String message) {
+		super(message);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/CryptoException.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,35 @@
+// 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.exceptions;
+
+/**
+ * Throws when problem with DES (or smth else) cryptosystem occured
+ */
+@SuppressWarnings("serial")
+public class CryptoException extends FatalException {
+	public CryptoException(String message, Throwable exception) {
+		super(message, exception);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/FatalException.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,35 @@
+// 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.exceptions;
+
+/**
+ * Trhows when further normal program execution unavailable
+ */
+@SuppressWarnings("serial")
+public class FatalException extends CommonException {
+	public FatalException(String message, Throwable e) {
+		super(message, e);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/ProtocolException.java	Thu Sep 11 07:30:03 2014 +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.exceptions;
+
+@SuppressWarnings("serial")
+public class ProtocolException extends CommonException {
+	public ProtocolException(String message) {
+		super(message);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/TransportException.java	Thu Sep 11 07:30:03 2014 +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.exceptions;
+
+
+@SuppressWarnings("serial")
+public class TransportException extends CommonException {
+	public TransportException(String message, Throwable exception) {
+		super(message, exception);
+	}
+	public TransportException(Throwable exception) {
+		super(exception);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/UnsupportedProtocolVersionException.java	Thu Sep 11 07:30:03 2014 +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.exceptions;
+
+@SuppressWarnings("serial")
+public class UnsupportedProtocolVersionException extends ProtocolException {
+	public UnsupportedProtocolVersionException(String message) {
+		super(message);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/exceptions/UnsupportedSecurityTypeException.java	Thu Sep 11 07:30:03 2014 +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.exceptions;
+
+@SuppressWarnings("serial")
+public class UnsupportedSecurityTypeException extends ProtocolException {
+	public UnsupportedSecurityTypeException(String message) {
+		super(message);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/CapabilityContainer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,107 @@
+// 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.rfb;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.EncodingType;
+import com.glavsoft.transport.Reader;
+
+/**
+ * Container for Tight extention protocol capabilities
+ *
+ * Needed to set what capabilities we support, which of them supported
+ * by server and for enabling/disabling ops on them.
+ */
+public class CapabilityContainer {
+	public CapabilityContainer() { }
+
+	private final Map<Integer, RfbCapabilityInfo> caps = new HashMap<Integer, RfbCapabilityInfo>();
+
+	public void add(RfbCapabilityInfo capabilityInfo) {
+		caps.put(capabilityInfo.getCode(), capabilityInfo);
+	}
+
+	public void add(int code, String vendor, String name) {
+		caps.put(code, new RfbCapabilityInfo(code, vendor, name));
+	}
+
+	public void addEnabled(int code, String vendor, String name) {
+		RfbCapabilityInfo capability = new RfbCapabilityInfo(code, vendor, name);
+		capability.setEnable(true);
+		caps.put(code, capability);
+	}
+
+	public void setEnable(int id, boolean enable) {
+		RfbCapabilityInfo c = caps.get(id);
+		if (c != null) {
+			c.setEnable(enable);
+		}
+	}
+
+	public void setAllEnable(boolean enable) {
+		for (RfbCapabilityInfo c : caps.values()) {
+			c.setEnable(enable);
+		}
+	}
+
+	public Collection<EncodingType> getEnabledEncodingTypes() {
+		Collection<EncodingType> types = new LinkedList<EncodingType>();
+		for (RfbCapabilityInfo c : caps.values()) {
+			if (c.isEnabled()) {
+				types.add(EncodingType.byId(c.getCode()));
+			}
+		}
+		return types;
+	}
+
+	public void read(Reader reader, int count) throws TransportException {
+		while (count-- > 0) {
+			RfbCapabilityInfo capInfoReceived = new RfbCapabilityInfo(reader);
+			Logger.getLogger("com.glavsoft.rfb").fine(capInfoReceived.toString());
+			RfbCapabilityInfo myCapInfo = caps.get(capInfoReceived.getCode());
+			if (myCapInfo != null) {
+				myCapInfo.setEnable(true);
+			}
+		}
+	}
+
+	public boolean isSupported(int code) {
+		RfbCapabilityInfo myCapInfo = caps.get(code);
+		if (myCapInfo != null)
+			return myCapInfo.isEnabled();
+		return false;
+	}
+
+	public boolean isSupported(RfbCapabilityInfo rfbCapabilityInfo) {
+		return isSupported(rfbCapabilityInfo.getCode());
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/ClipboardController.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,59 @@
+// 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.rfb;
+
+/**
+ * Interface for handling clipboard texts
+ */
+public interface ClipboardController extends IChangeSettingsListener {
+	void updateSystemClipboard(byte[] bytes);
+
+	/**
+	 * Get text clipboard contens when needed send to remote, or null vise versa
+	 * Implement this method such a way in swing context, because swing's clipboard
+	 * update listener invoked only on DataFlavor changes not content changes.
+	 * Implement as returned null on systems where clipboard listeners work correctly.
+	 *
+	 * @return clipboad string contents if it is changed from last method call
+	 * or null when clipboard contains non text object or clipboard contents didn't changed
+	 */
+	String getRenewedClipboardText();
+
+	/**
+	 * Returns clipboard text content previously retrieved frim system clipboard by
+	 * updateSavedClippoardContent()
+	 *
+	 * @return clipboard text content
+	 */
+	String getClipboardText();
+
+	/**
+	 * Enable/disable clipboard transfer
+	 *
+	 * @param enable
+	 */
+	void setEnabled(boolean enable);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/IChangeSettingsListener.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,34 @@
+// 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.rfb;
+
+import com.glavsoft.core.SettingsChangedEvent;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public interface IChangeSettingsListener {
+	void settingsChanged(SettingsChangedEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/IPasswordRetriever.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,29 @@
+// 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.rfb;
+
+public interface IPasswordRetriever {
+	String getPassword();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/IRepaintController.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,42 @@
+// 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.rfb;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
+import com.glavsoft.transport.Reader;
+
+/**
+ * Interface for sending repaint event from worker thread to GUI thread
+ */
+public interface IRepaintController extends IChangeSettingsListener {
+	void repaintBitmap(FramebufferUpdateRectangle rect);
+	void repaintBitmap(int x, int y, int width, int height);
+	void repaintCursor();
+	void updateCursorPosition(short x, short y);
+	Renderer createRenderer(Reader reader, int width, int height, PixelFormat pixelFormat);
+	void setPixelFormat(PixelFormat pixelFormat);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/IRfbSessionListener.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,39 @@
+// 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.rfb;
+
+/**
+ * Fires Rfb session [start]-stop events
+ */
+public interface IRfbSessionListener {
+
+	/**
+	 * Fired after rfb session closed (threads stopped, other resources fried).
+	 * Note: this event may be fired from unpredictable thread.
+	 * @param reason reason to show why session closed
+	 */
+	void rfbSessionStopped(String reason);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/RfbCapabilityInfo.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,122 @@
+// 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.rfb;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+/**
+ * Structure used to describe protocol options such as tunneling methods,
+ * authentication schemes and message types (protocol versions 3.7t, 3.8t).
+ * typedef struct _rfbCapabilityInfo {
+ *  CARD32 code;                // numeric identifier
+ *  CARD8 vendorSignature[4];   // vendor identification
+ *  CARD8 nameSignature[8];     // abbreviated option name
+ * } rfbCapabilityInfo;
+ */
+public class RfbCapabilityInfo {
+	/*
+	 * Vendors known by TightVNC: standard VNC/RealVNC, TridiaVNC, and TightVNC.
+	 * #define rfbStandardVendor "STDV"
+	 * #define rfbTridiaVncVendor "TRDV"
+	 * #define rfbTightVncVendor "TGHT"
+	 */
+	public static final String VENDOR_STANDARD = "STDV";
+	public static final String VENDOR_TRIADA = "TRDV";
+	public static final String VENDOR_TIGHT = "TGHT";
+
+	public static final String TUNNELING_NO_TUNNELING = "NOTUNNEL";
+
+	public static final String AUTHENTICATION_NO_AUTH = "NOAUTH__";
+	public static final String AUTHENTICATION_VNC_AUTH ="VNCAUTH_";
+
+	public static final String ENCODING_COPYRECT = "COPYRECT";
+	public static final String ENCODING_HEXTILE = "HEXTILE_";
+	public static final String ENCODING_ZLIB = "ZLIB____";
+	public static final String ENCODING_ZRLE = "ZRLE____";
+	public static final String ENCODING_RRE = "RRE_____";
+	public static final String ENCODING_TIGHT = "TIGHT___";
+	// "Pseudo" encoding types
+	public static final String ENCODING_RICH_CURSOR = "RCHCURSR";
+	public static final String ENCODING_CURSOR_POS = "POINTPOS";
+	public static final String ENCODING_DESKTOP_SIZE = "NEWFBSIZ";
+
+	private int code;
+	private String vendorSignature;
+	private String nameSignature;
+	private boolean enable;
+
+	public RfbCapabilityInfo(int code, String vendorSignature, String nameSignature) {
+		this.code = code;
+		this.vendorSignature = vendorSignature;
+		this.nameSignature = nameSignature;
+		enable = true;
+	}
+
+	public RfbCapabilityInfo(Reader reader) throws TransportException {
+		code = reader.readInt32();
+		vendorSignature = reader.readString(4);
+		nameSignature = reader.readString(8);
+	}
+
+	@Override
+	public boolean equals(Object otherObj) {
+		if (this == otherObj) { return true; }
+		if (null == otherObj) { return false; }
+		if (getClass() != otherObj.getClass()) { return false; }
+		RfbCapabilityInfo other = (RfbCapabilityInfo) otherObj;
+		return code == other.code &&
+			vendorSignature.equals(other.vendorSignature) &&
+			nameSignature.equals(other.nameSignature);
+	}
+
+	public void setEnable(boolean enable) {
+		this.enable = enable;
+	}
+
+	public int getCode() {
+		return code;
+	}
+
+	public String getVendorSignature() {
+		return vendorSignature;
+	}
+
+	public String getNameSignature() {
+		return nameSignature;
+	}
+
+	public boolean isEnabled() {
+		return enable;
+	}
+
+	@Override
+	public String toString() {
+		return "RfbCapabilityInfo: [code: " + code +
+		", vendor: " + vendorSignature +
+		", name: " + nameSignature +
+		"]";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/ClientCutTextMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,61 @@
+// 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.rfb.client;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Writer;
+
+/**
+ * ClientCutText
+ * The client has new ISO 8859-1 (Latin-1) text in its cut buffer. Ends of lines are repre-
+ * sented by the linefeed / newline character (value 10) alone. No carriage-return (value
+ * 13) is needed. There is currently no way to transfer text outside the Latin-1 character
+ * set.
+ * 1      - U8       - 6
+ * 3      -          - padding
+ * 4      - U32      - length
+ * length - U8 array - text
+ */
+public class ClientCutTextMessage implements ClientToServerMessage {
+	private final byte [] bytes;
+
+	public ClientCutTextMessage(byte[] bytes) {
+		this.bytes = bytes;
+	}
+
+	@Override
+	public void send(Writer writer) throws TransportException {
+		writer.write(CLIENT_CUT_TEXT);
+		writer.writeByte(0); writer.writeInt16(0); // padding
+		writer.write(bytes.length);
+		writer.write(bytes); // TODO: [dime] convert 'text' String to byte arrya using right charset
+		writer.flush();
+	}
+
+	@Override
+	public String toString() {
+		return "ClientCutTextMessage: [length: " + bytes.length +", text: ...]";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/ClientToServerMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,39 @@
+// 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.rfb.client;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Writer;
+
+public interface ClientToServerMessage {
+	byte SET_PIXEL_FORMAT = 0;
+	byte SET_ENCODINGS = 2;
+	byte FRAMEBUFFER_UPDATE_REQUEST = 3;
+	byte KEY_EVENT = 4;
+	byte POINTER_EVENT = 5;
+	byte CLIENT_CUT_TEXT = 6;
+
+	void send(Writer writer) throws TransportException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/FramebufferUpdateRequestMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,65 @@
+// 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.rfb.client;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Writer;
+
+
+public class FramebufferUpdateRequestMessage implements ClientToServerMessage {
+	private final boolean incremental;
+	private final int height;
+	private final int width;
+	private final int y;
+	private final int x;
+
+	public FramebufferUpdateRequestMessage(int x, int y, int width,
+			int height, boolean incremental) {
+		this.x = x;
+		this.y = y;
+		this.width = width;
+		this.height = height;
+		this.incremental = incremental;
+	}
+
+	@Override
+	public void send(Writer writer) throws TransportException {
+		writer.writeByte(FRAMEBUFFER_UPDATE_REQUEST);
+		writer.writeByte(incremental ? 1 : 0);
+		writer.writeInt16(x);
+		writer.writeInt16(y);
+		writer.writeInt16(width);
+		writer.writeInt16(height);
+		writer.flush();
+	}
+
+	@Override
+	public String toString() {
+		return "FramebufferUpdateRequestMessage: [x: " + x + " y: " + y
+		+ " width: " + width + " height: " + height +
+		" incremental: " + incremental + "]";
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/KeyEventMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,67 @@
+// 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.rfb.client;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Writer;
+
+/**
+ * A key press or release. Down-flag is non-zero (true) if the key is now pressed, zero
+ * (false) if it is now released. The key itself is specified using the “keysym” values
+ * defined by the X Window System.
+ * 1 - U8  - message-type
+ * 1 - U8  - down-flag
+ * 2 - -   - padding
+ * 4 - U32 - key
+ * For most ordinary keys, the “keysym” is the same as the corresponding ASCII value.
+ * For full details, see The Xlib Reference Manual, published by O’Reilly & Associates,
+ * or see the header file <X11/keysymdef.h> from any X Window System installa-
+ * tion.
+ */
+public class KeyEventMessage implements ClientToServerMessage {
+
+	private final int key;
+	private final boolean downFlag;
+
+	public KeyEventMessage(int key, boolean downFlag) {
+		this.downFlag = downFlag;
+		this.key = key;
+	}
+
+	@Override
+	public void send(Writer writer) throws TransportException {
+		writer.writeByte(KEY_EVENT);
+		writer.writeByte(downFlag ? 1 : 0);
+		writer.writeInt16(0); // padding
+		writer.write(key);
+		writer.flush();
+	}
+
+	@Override
+	public String toString() {
+		return "[KeyEventMessage: [down-flag: "+downFlag + ", key: " + key +"("+Integer.toHexString(key)+")]";
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/PointerEventMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,56 @@
+// 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.rfb.client;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Writer;
+
+public class PointerEventMessage implements ClientToServerMessage {
+	private final byte buttonMask;
+	private final short x;
+	private final short y;
+
+	public PointerEventMessage(byte buttonMask, short x, short y) {
+		this.buttonMask = buttonMask;
+		this.x = x;
+		this.y = y;
+	}
+
+	@Override
+	public void send(Writer writer) throws TransportException {
+		writer.writeByte(POINTER_EVENT);
+		writer.writeByte(buttonMask);
+		writer.writeInt16(x);
+		writer.writeInt16(y);
+		writer.flush();
+	}
+
+	@Override
+	public String toString() {
+		return "PointerEventMessage: [x: "+ x +", y: "+ y + ", button-mask: " +
+		buttonMask +"]";
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/SetEncodingsMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,61 @@
+// 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.rfb.client;
+
+import java.util.Set;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.EncodingType;
+import com.glavsoft.transport.Writer;
+
+public class SetEncodingsMessage implements ClientToServerMessage {
+	private final Set<EncodingType> encodings;
+
+	public SetEncodingsMessage(Set<EncodingType> set) {
+		this.encodings = set;
+	}
+
+	@Override
+	public void send(Writer writer) throws TransportException {
+		writer.writeByte(SET_ENCODINGS);
+		writer.writeByte(0); // padding byte
+		writer.writeInt16(encodings.size());
+		for (EncodingType enc : encodings) {
+			writer.writeInt32(enc.getId());
+		}
+		writer.flush();
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder("SetEncodingsMessage: [encodings: ");
+		for (EncodingType enc : encodings) {
+			sb.append(enc.name()).append(',');
+		}
+		sb.setLength(sb.length()-1);
+		return sb.append(']').toString();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/client/SetPixelFormatMessage.java	Thu Sep 11 07:30:03 2014 +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.rfb.client;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.transport.Writer;
+
+public class SetPixelFormatMessage implements ClientToServerMessage {
+	private final PixelFormat pixelFormat;
+
+	public SetPixelFormatMessage(PixelFormat pixelFormat) {
+		this.pixelFormat = pixelFormat;
+	}
+
+	@Override
+	public void send(Writer writer) throws TransportException {
+		writer.writeByte(SET_PIXEL_FORMAT);
+		writer.writeInt16(0);
+		writer.writeByte(0);
+		pixelFormat.send(writer);
+		writer.flush();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/EncodingType.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,147 @@
+// 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.rfb.encoding;
+
+import java.util.LinkedHashSet;
+
+/**
+ * Encoding types
+ */
+public enum EncodingType {
+	/**
+	 * Desktop data representes as raw bytes stream
+	 */
+	RAW_ENCODING(0, "Raw"),
+	/**
+	 * Specfies encodings which allow to copy part of image in client's
+	 * framebuffer from one place to another.
+	 */
+	COPY_RECT(1, "CopyRect"),
+	RRE(2, "RRE"),
+	/**
+	 *  Hextile encoding, uses palettes, filling and raw subencoding
+	 */
+    HEXTILE(5, "Hextile"),
+    /**
+     * This encoding is like raw but previously all data compressed with zlib.
+     */
+    ZLIB(6, "ZLib"),
+	/**
+	 * Tight Encoding for slow connection. It is uses raw data, palettes, filling
+	 * and jpeg subencodings
+	 */
+	TIGHT(7, "Tight"),
+    //ZlibHex(8),
+	/**
+	 * ZRLE Encoding is like Hextile but previously all data compressed with zlib.
+	 */
+    ZRLE(16, "ZRLE"),
+
+    /**
+     * Rich Cursor pseudo encoding which allows to transfer cursor shape
+     * with transparency
+     */
+    RICH_CURSOR(0xFFFFFF11, "RichCursor"),
+    /**
+     * Desktop Size Pseudo encoding allows to notificate client about
+     *  remote screen resolution changed.
+     */
+    DESKTOP_SIZE(0xFFFFFF21, "DesctopSize"),
+    /**
+     * Cusros position encoding allows to transfer remote cursor position to
+     * client side.
+     */
+	CURSOR_POS(0xFFFFFF18, "CursorPos"),
+
+	COMPRESS_LEVEL_0(0xFFFFFF00 + 0, "CompressionLevel0"),
+	COMPRESS_LEVEL_1(0xFFFFFF00 + 1, "CompressionLevel1"),
+	COMPRESS_LEVEL_2(0xFFFFFF00 + 2, "CompressionLevel2"),
+	COMPRESS_LEVEL_3(0xFFFFFF00 + 3, "CompressionLevel3"),
+	COMPRESS_LEVEL_4(0xFFFFFF00 + 4, "CompressionLevel4"),
+	COMPRESS_LEVEL_5(0xFFFFFF00 + 5, "CompressionLevel5"),
+	COMPRESS_LEVEL_6(0xFFFFFF00 + 6, "CompressionLevel6"),
+	COMPRESS_LEVEL_7(0xFFFFFF00 + 7, "CompressionLevel7"),
+	COMPRESS_LEVEL_8(0xFFFFFF00 + 8, "CompressionLevel8"),
+	COMPRESS_LEVEL_9(0xFFFFFF00 + 9, "CompressionLevel9"),
+
+	JPEG_QUALITY_LEVEL_0(0xFFFFFFE0 + 0, "JpegQualityLevel0"),
+	JPEG_QUALITY_LEVEL_1(0xFFFFFFE0 + 1, "JpegQualityLevel1"),
+	JPEG_QUALITY_LEVEL_2(0xFFFFFFE0 + 2, "JpegQualityLevel2"),
+	JPEG_QUALITY_LEVEL_3(0xFFFFFFE0 + 3, "JpegQualityLevel3"),
+	JPEG_QUALITY_LEVEL_4(0xFFFFFFE0 + 4, "JpegQualityLevel4"),
+	JPEG_QUALITY_LEVEL_5(0xFFFFFFE0 + 5, "JpegQualityLevel5"),
+	JPEG_QUALITY_LEVEL_6(0xFFFFFFE0 + 6, "JpegQualityLevel6"),
+	JPEG_QUALITY_LEVEL_7(0xFFFFFFE0 + 7, "JpegQualityLevel7"),
+	JPEG_QUALITY_LEVEL_8(0xFFFFFFE0 + 8, "JpegQualityLevel8"),
+	JPEG_QUALITY_LEVEL_9(0xFFFFFFE0 + 9, "JpegQualityLevel9");
+
+	private int id;
+	private final String name;
+	private EncodingType(int id, String name) {
+		this.id = id;
+		this.name = name;
+	}
+
+	public int getId() {
+		return id;
+	}
+	public String getName() {
+		return name;
+	}
+
+	public static LinkedHashSet<EncodingType> ordinaryEncodings = new LinkedHashSet<EncodingType>();
+	static {
+		ordinaryEncodings.add(TIGHT);
+		ordinaryEncodings.add(HEXTILE);
+		ordinaryEncodings.add(ZRLE);
+		ordinaryEncodings.add(ZLIB);
+		ordinaryEncodings.add(RRE);
+		ordinaryEncodings.add(COPY_RECT);
+//		ordinaryEncodings.add(RAW_ENCODING);
+	}
+
+	public static LinkedHashSet<EncodingType> pseudoEncodings = new LinkedHashSet<EncodingType>();
+	static {
+		pseudoEncodings.add(RICH_CURSOR);
+		pseudoEncodings.add(CURSOR_POS);
+		pseudoEncodings.add(DESKTOP_SIZE);
+	}
+
+	public static LinkedHashSet<EncodingType> compressionEncodings = new LinkedHashSet<EncodingType>();
+	static {
+		compressionEncodings.add(COMPRESS_LEVEL_0);
+		compressionEncodings.add(JPEG_QUALITY_LEVEL_0);
+	}
+
+	public static EncodingType byId(int id) {
+		// TODO needs to speedup with hash usage?
+		for (EncodingType type : values()) {
+			if (type.getId() == id)
+				return type;
+		}
+		throw new IllegalArgumentException("Unsupported encoding id: " + id);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/PixelFormat.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,187 @@
+// 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.rfb.encoding;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+/**
+ * Pixel Format:
+ * 1 - U8  - bits-per-pixel
+ * 1 - U8  - depth
+ * 1 - U8  - big-endian-flag
+ * 1 - U8  - true-color-flag
+ * 2 - U16 - red-max
+ * 2 - U16 - green-max
+ * 2 - U16 - blue-max
+ * 1 - U8  - red-shift
+ * 1 - U8  - green-shift
+ * 1 - U8  - blue-shift
+ * 3 -     - padding
+ */
+public class PixelFormat {
+	public byte bitsPerPixel;
+	public byte depth;
+	public byte bigEndianFlag;
+	public byte trueColourFlag;
+	public short redMax;
+	public short greenMax;
+	public short blueMax;
+	public byte redShift;
+	public byte greenShift;
+	public byte blueShift;
+
+	public void fill(Reader reader) throws TransportException {
+		bitsPerPixel = reader.readByte();
+		depth = reader.readByte();
+	    bigEndianFlag = reader.readByte();
+	    trueColourFlag = reader.readByte();
+	    redMax = reader.readInt16();
+	    greenMax = reader.readInt16();
+	    blueMax = reader.readInt16();
+	    redShift = reader.readByte();
+	    greenShift = reader.readByte();
+	    blueShift = reader.readByte();
+	    reader.readBytes(3); // skip padding bytes
+	}
+
+    public void send(Writer writer) throws TransportException {
+    	writer.write(bitsPerPixel);
+    	writer.write(depth);
+    	writer.write(bigEndianFlag);
+    	writer.write(trueColourFlag);
+    	writer.write(redMax);
+    	writer.write(greenMax);
+    	writer.write(blueMax);
+    	writer.write(redShift);
+    	writer.write(greenShift);
+    	writer.write(blueShift);
+    	writer.writeInt16(0); // padding bytes
+    	writer.writeByte(0); // padding bytes
+    }
+
+    public static PixelFormat create24bitColorDepthPixelFormat(int bigEndianFlag) {
+    	final PixelFormat pixelFormat = new PixelFormat();
+    	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
+		pixelFormat.bitsPerPixel = 32;
+		pixelFormat.blueMax = 255;
+		pixelFormat.blueShift = 0;
+		pixelFormat.greenMax = 255;
+		pixelFormat.greenShift = 8;
+		pixelFormat.redMax = 255;
+		pixelFormat.redShift = 16;
+		pixelFormat.depth = 24;
+		pixelFormat.trueColourFlag = 1;
+		return pixelFormat;
+    }
+
+    /**
+     * specifies 65536 colors, 5bit per Red, 6bit per Green, 5bit per Blue
+     */
+    public static PixelFormat create16bitColorDepthPixelFormat(int bigEndianFlag) {
+    	final PixelFormat pixelFormat = new PixelFormat();
+    	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
+		pixelFormat.bitsPerPixel = 16;
+		pixelFormat.blueMax = 31;
+		pixelFormat.blueShift = 0;
+		pixelFormat.greenMax = 63;
+		pixelFormat.greenShift = 5;
+		pixelFormat.redMax = 31;
+		pixelFormat.redShift = 11;
+		pixelFormat.depth = 16;
+		pixelFormat.trueColourFlag = 1;
+		return pixelFormat;
+    }
+
+    /**
+     * specifies 256 colors, 2bit per Blue, 3bit per Green & Red
+     */
+    public static PixelFormat create8bitColorDepthBGRPixelFormat(int bigEndianFlag) {
+    	final PixelFormat pixelFormat = new PixelFormat();
+    	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
+		pixelFormat.bitsPerPixel = 8;
+		pixelFormat.redMax = 7;
+		pixelFormat.redShift = 0;
+		pixelFormat.greenMax = 7;
+		pixelFormat.greenShift = 3;
+		pixelFormat.blueMax = 3;
+		pixelFormat.blueShift = 6;
+		pixelFormat.depth = 8;
+		pixelFormat.trueColourFlag = 1;
+		return pixelFormat;
+    }
+
+    /**
+     * specifies 64 colors, 2bit per Red, Green & Blue
+     */
+    public static PixelFormat create6bitColorDepthPixelFormat(int bigEndianFlag) {
+    	final PixelFormat pixelFormat = new PixelFormat();
+    	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
+		pixelFormat.bitsPerPixel = 8;
+		pixelFormat.blueMax = 3;
+		pixelFormat.blueShift = 0;
+		pixelFormat.greenMax = 3;
+		pixelFormat.greenShift = 2;
+		pixelFormat.redMax = 3;
+		pixelFormat.redShift = 4;
+		pixelFormat.depth = 6;
+		pixelFormat.trueColourFlag = 1;
+		return pixelFormat;
+    }
+
+    /**
+     * specifies 8 colors, 1bit per Red, Green & Blue
+     */
+    public static PixelFormat create3bppPixelFormat(int bigEndianFlag) {
+    	final PixelFormat pixelFormat = new PixelFormat();
+    	pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
+		pixelFormat.bitsPerPixel = 8;
+		pixelFormat.blueMax = 1;
+		pixelFormat.blueShift = 0;
+		pixelFormat.greenMax = 1;
+		pixelFormat.greenShift = 1;
+		pixelFormat.redMax = 1;
+		pixelFormat.redShift = 2;
+		pixelFormat.depth = 3;
+		pixelFormat.trueColourFlag = 1;
+		return pixelFormat;
+    }
+
+    @Override
+    public String toString() {
+    	return "PixelFormat: [bits-per-pixel: " + String.valueOf(0xff & bitsPerPixel) +
+    	", depth: " + String.valueOf(0xff & depth) +
+    	", big-endian-flag: " + String.valueOf(0xff & bigEndianFlag) +
+    	", true-color-flag: " + String.valueOf(0xff & trueColourFlag) +
+    	", red-max: " + String.valueOf(0xffff & redMax) +
+    	", green-max: " + String.valueOf(0xffff & greenMax) +
+    	", blue-max: " + String.valueOf(0xffff & blueMax) +
+    	", red-shift: " + String.valueOf(0xff & redShift) +
+    	", green-shift: " + String.valueOf(0xff & greenShift) +
+    	", blue-shift: " + String.valueOf(0xff & blueShift) +
+    	"]";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/ServerInitMessage.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,80 @@
+// 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.rfb.encoding;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+/**
+ * Struct filled from the ServerInit message
+ * 2  - U16         - framebuffer-width
+ * 2  - U16         - framebuffer-height
+ * 16 - PixelFormat - server-pixel-format
+ * 4  - U32         - name-length
+ * name-length - U8 array - name-string
+ */
+public class ServerInitMessage {
+	protected int frameBufferWidth;
+    protected int frameBufferHeight;
+    protected PixelFormat pixelFormat;
+    protected String name;
+
+	public ServerInitMessage(Reader reader) throws TransportException {
+		frameBufferWidth = reader.readUInt16();
+		frameBufferHeight = reader.readUInt16();
+		pixelFormat = new PixelFormat();
+		pixelFormat.fill(reader);
+		name = reader.readString();
+	}
+
+	protected ServerInitMessage() {
+		// empty
+	}
+
+	public int getFrameBufferWidth() {
+		return frameBufferWidth;
+	}
+
+	public int getFrameBufferHeight() {
+		return frameBufferHeight;
+	}
+
+	public PixelFormat getPixelFormat() {
+		return pixelFormat;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+    @Override
+    public String toString() {
+    	return "ServerInitMessage: [name: "+ name +
+    	", framebuffer-width: " + String.valueOf(frameBufferWidth) +
+    	", framebuffer-height: " + String.valueOf(frameBufferHeight) +
+    	", server-pixel-format: " + pixelFormat +
+    	"]";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ByteBuffer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,58 @@
+// 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.rfb.encoding.decoder;
+
+/**
+ * Resizeable to needed length byte buffer
+ * Singleton for share among decoders.
+ */
+public class ByteBuffer {
+	private static ByteBuffer instance = new ByteBuffer();
+	private byte [] buffer = new byte[0];
+
+	private ByteBuffer() { /*empty*/ }
+	public static ByteBuffer getInstance() {
+		return instance;
+	}
+
+	/**
+	 * Checks for buffer capacity is enougth ( < length) and enlarge it if not
+	 *
+	 * @param length
+	 */
+	public void correctBufferCapacity(int length) {
+		// procondition: buffer != null
+		assert (buffer != null);
+		if (buffer.length < length) {
+			buffer = new byte[length];
+		}
+	}
+
+	public byte[] getBuffer(int length) {
+		correctBufferCapacity(length);
+		return buffer;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/CopyRectDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,42 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+public class CopyRectDecoder extends Decoder {
+
+	@Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int srcX = reader.readUInt16();
+		int srcY = reader.readUInt16();
+		if (rect.width == 0  || rect.height == 0) return;
+		renderer.copyRect(srcX, srcY, rect);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/Decoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,45 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+
+public abstract class Decoder {
+
+	/**
+	 * Decode rectangle data.
+	 */
+	abstract public void decode(Reader reader, Renderer renderer,
+    		FramebufferUpdateRectangle rect) throws TransportException;
+
+	/**
+	 * Reset decoder when needed. Ex. reset ZLib stream inflaters for Z* and Tight decoders.
+	 */
+    public void reset() { /*empty*/ }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/DecodersContainer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,93 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.rfb.encoding.EncodingType;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * Decoders container class
+ */
+public class DecodersContainer {
+	private static Map<EncodingType, Class<? extends Decoder>> knownDecoders =
+		new HashMap<EncodingType, Class<? extends Decoder>>();
+	static {
+		knownDecoders.put(EncodingType.TIGHT, TightDecoder.class);
+		knownDecoders.put(EncodingType.HEXTILE, HextileDecoder.class);
+		knownDecoders.put(EncodingType.ZRLE, ZRLEDecoder.class);
+		knownDecoders.put(EncodingType.ZLIB, ZlibDecoder.class);
+		knownDecoders.put(EncodingType.RRE, RREDecoder.class);
+		knownDecoders.put(EncodingType.COPY_RECT, CopyRectDecoder.class);
+//		knownDecoders.put(EncodingType.RAW_ENCODING, RawDecoder.class);
+	}
+	private final Map<EncodingType, Decoder> decoders =
+		new HashMap<EncodingType, Decoder>();
+
+	public DecodersContainer() {
+		decoders.put(EncodingType.RAW_ENCODING, RawDecoder.getInstance());
+	}
+
+	/**
+	 * Instantiate decoders for encodings we are going to use.
+	 *
+	 * @param encodings encodings we need to handle
+	 */
+	public void instantiateDecodersWhenNeeded(Collection<EncodingType> encodings) {
+		for (EncodingType enc : encodings) {
+			if (EncodingType.ordinaryEncodings.contains(enc) && ! decoders.containsKey(enc)) {
+				try {
+					decoders.put(enc, knownDecoders.get(enc).newInstance());
+				} catch (InstantiationException e) {
+					logError(enc, e);
+				} catch (IllegalAccessException e) {
+					logError(enc, e);
+				}
+			}
+		}
+	}
+
+	private void logError(EncodingType enc, Exception e) {
+		Logger.getLogger(this.getClass().getName()).severe("Can not instantiate decoder for encoding type '" +
+				enc.getName() + "' " + e.getMessage());
+	}
+
+	public Decoder getDecoderByType(EncodingType type) {
+		return decoders.get(type);
+	}
+
+	public void resetDecoders() {
+		for (Decoder decoder : decoders.values()) {
+			if (decoder != null) {
+				decoder.reset();
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/FramebufferUpdateRectangle.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,77 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.EncodingType;
+import com.glavsoft.transport.Reader;
+
+/**
+ * Header for framebuffer-update-rectangle header server message
+ * 2 - U16 - x-position
+ * 2 - U16 - y-position
+ * 2 - U16 - width
+ * 2 - U16 - height
+ * 4 - S32 - encoding-type
+ * and then follows the pixel data in the specified encoding
+ */
+public class FramebufferUpdateRectangle {
+	public int x;
+	public int y;
+	public int width;
+	public int height;
+    private EncodingType encodingType;
+
+	public FramebufferUpdateRectangle() {
+		// nop
+	}
+
+	public FramebufferUpdateRectangle(int x, int y, int w, int h) {
+		this.x = x; this.y = y;
+		width = w; height = h;
+	}
+
+	public void fill(Reader reader) throws TransportException {
+    	x = reader.readUInt16();
+        y = reader.readUInt16();
+        width = reader.readUInt16();
+        height = reader.readUInt16();
+        int encoding = reader.readInt32();
+		encodingType = EncodingType.byId(encoding);
+    }
+
+	public EncodingType getEncodingType() {
+		return encodingType;
+	}
+
+	@Override
+	public String toString() {
+		return "FramebufferUpdateRect: [x: " + x + ", y: " + y +
+			", width: " + width + ", height: " + height +
+			", encodingType: " + encodingType +
+			"]";
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/HextileDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,110 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+
+public class HextileDecoder extends Decoder {
+	private static final int DEFAULT_TILE_SIZE = 16;
+	private static final int RAW_MASK = 1;
+	private static final int BACKGROUND_SPECIFIED_MASK = 2;
+	private static final int FOREGROUND_SPECIFIED_MASK = 4;
+	private static final int ANY_SUBRECTS_MASK = 8;
+	private static final int SUBRECTS_COLOURED_MASK = 16;
+	private static final int FG_COLOR_INDEX = 0;
+	private static final int BG_COLOR_INDEX = 1;
+
+	@Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		if (rect.width == 0  || rect.height == 0) return;
+		int[] colors = new int[] {-1, -1};
+		int maxX = rect.x + rect.width;
+		int maxY = rect.y + rect.height;
+		for (int tileY = rect.y; tileY < maxY;
+		tileY += DEFAULT_TILE_SIZE) {
+			int tileHeight = Math.min(maxY - tileY, DEFAULT_TILE_SIZE);
+			for (int tileX = rect.x; tileX < maxX;
+			tileX += DEFAULT_TILE_SIZE) {
+				int tileWidth = Math.min(maxX - tileX, DEFAULT_TILE_SIZE);
+				decodeHextileSubrectangle(reader, renderer, colors, tileX,
+						tileY, tileWidth, tileHeight);
+			}
+		}
+
+	}
+
+	private void decodeHextileSubrectangle(Reader reader,
+			Renderer renderer, int[] colors,
+			int tileX, int tileY, int tileWidth, int tileHeight)
+			throws TransportException {
+
+		int subencoding = reader.readUInt8();
+
+		if ((subencoding & RAW_MASK) != 0) {
+			RawDecoder.getInstance().decode(reader, renderer,
+					tileX, tileY, tileWidth, tileHeight);
+			return;
+		}
+
+		if ((subencoding & BACKGROUND_SPECIFIED_MASK) != 0) {
+			colors[BG_COLOR_INDEX] = renderer.readPixelColor(reader);
+		}
+		assert colors[BG_COLOR_INDEX] != -1;
+		renderer.fillRect(colors[BG_COLOR_INDEX],
+				tileX, tileY, tileWidth, tileHeight);
+
+		if ((subencoding & FOREGROUND_SPECIFIED_MASK) != 0) {
+			colors[FG_COLOR_INDEX] = renderer.readPixelColor(reader);
+		}
+
+		if ((subencoding & ANY_SUBRECTS_MASK) == 0)
+			return;
+
+		int numberOfSubrectangles = reader.readUInt8();
+		boolean colorSpecified =
+			(subencoding & SUBRECTS_COLOURED_MASK) != 0;
+		for (int i = 0; i < numberOfSubrectangles; ++i) {
+			if (colorSpecified) {
+				colors[FG_COLOR_INDEX] = renderer.readPixelColor(reader);
+			}
+			byte dimensions = reader.readByte(); // bits 7-4 for x, bits 3-0 for y
+			int subtileX = dimensions >> 4 & 0x0f;
+			int subtileY = dimensions & 0x0f;
+			dimensions = reader.readByte(); // bits 7-4 for w, bits 3-0 for h
+			int subtileWidth = 1 + (dimensions >> 4 & 0x0f);
+			int subtileHeight = 1 + (dimensions & 0x0f);
+			assert colors[FG_COLOR_INDEX] != -1;
+			renderer.fillRect(colors[FG_COLOR_INDEX],
+					tileX + subtileX, tileY + subtileY,
+					subtileWidth, subtileHeight);
+		}
+	}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/RREDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,50 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+public class RREDecoder extends Decoder {
+
+	@Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int numOfSubrectangles = reader.readInt32();
+		int color = renderer.readPixelColor(reader);
+		renderer.fillRect(color, rect);
+		for (int i = 0; i < numOfSubrectangles; ++i) {
+			color = renderer.readPixelColor(reader);
+			int x = reader.readUInt16();
+			int y = reader.readUInt16();
+			int width = reader.readUInt16();
+			int height = reader.readUInt16();
+			renderer.fillRect(color, rect.x + x, rect.y + y, width, height);
+		}
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/RawDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,52 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+public class RawDecoder extends Decoder {
+	private static RawDecoder instance = new RawDecoder();
+	public static RawDecoder getInstance() {
+		return instance;
+	}
+	private RawDecoder() { /*empty*/ }
+
+	@Override
+	public void decode(Reader reader,
+			Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
+		decode(reader, renderer, rect.x, rect.y, rect.width, rect.height);
+	}
+
+	public void decode(Reader reader, Renderer renderer, int x, int y,
+			int width, int height) throws TransportException {
+		int length = width * height * renderer.getBytesPerPixel();
+		byte [] bytes = ByteBuffer.getInstance().getBuffer(length);
+		reader.readBytes(bytes, 0, length);
+		renderer.drawBytes(bytes, x, y, width, height);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/RichCursorDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,81 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+/**
+ * Decoder for RichCursor pseudo encoding
+ */
+public class RichCursorDecoder extends Decoder {
+	private static RichCursorDecoder instance = new RichCursorDecoder();
+
+	private RichCursorDecoder() { /*empty*/ }
+
+	public static RichCursorDecoder getInstance() {
+		return instance;
+	}
+
+	@Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int bytesPerPixel = renderer.getBytesPerPixel();
+		int length = rect.width * rect.height * bytesPerPixel;
+		if (0 == length)
+			return;
+		byte[] buffer = ByteBuffer.getInstance().getBuffer(length);
+		reader.readBytes(buffer, 0, length);
+
+		StringBuilder sb = new StringBuilder(" ");
+		for (int i=0; i<length; ++i) {
+			sb.append(Integer.toHexString(buffer[i]&0xff)).append(" ");
+		}
+		int scanLine = (int) Math.floor((rect.width + 7) / 8);
+		byte[] bitmask = new byte[scanLine * rect.height];
+		reader.readBytes(bitmask, 0, bitmask.length);
+
+		sb = new StringBuilder(" ");
+		for (int i=0; i<bitmask.length; ++i) {
+			sb.append(Integer.toHexString(bitmask[i]&0xff)).append(" ");
+		}
+		int[] cursorPixels = new int[rect.width * rect.height];
+		for (int y = 0; y < rect.height; ++y) {
+			for (int x = 0; x < rect.width; ++x) {
+				int offset = y * rect.width + x;
+				cursorPixels[offset] = isBitSet(bitmask[y * scanLine + x / 8], x % 8) ?
+					0xFF000000 | renderer.getPixelColor(buffer, offset * bytesPerPixel) :
+					0; // transparent
+			}
+		}
+		renderer.createCursor(cursorPixels, rect);
+	}
+
+	private boolean isBitSet(byte aByte, int index) {
+		return (aByte & 1 << 7 - index) > 0;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/TightDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,292 @@
+// 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.rfb.encoding.decoder;
+
+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
+ */
+public class TightDecoder extends Decoder {
+	private static Logger logger = Logger.getLogger("com.glavsoft.rfb.encoding.decoder");
+
+    private static final int FILL_TYPE = 0x08;
+    private static final int JPEG_TYPE = 0x09;
+
+    private static final int FILTER_ID_MASK = 0x40;
+    private static final int STREAM_ID_MASK = 0x30;
+
+    private static final int BASIC_FILTER = 0x00;
+    private static final int PALETTE_FILTER = 0x01;
+    private static final int GRADIENT_FILTER = 0x02;
+    private static final int MIN_SIZE_TO_COMPRESS = 12;
+
+    static final int DECODERS_NUM = 4;
+	Inflater[] decoders;
+
+    private int decoderId;
+    private int[] palette;
+
+    public TightDecoder() {
+		reset();
+	}
+
+	@Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int bytesPerPixel = renderer.getBytesPerPixelTight();
+
+		/**
+		 * bits
+		 * 7 - FILL or JPEG type
+		 * 6 - filter presence flag
+		 * 5, 4 - decoder to use when Basic type (bit 7 not set)
+		 *    or
+		 * 4 - JPEG type when set bit 7
+		 * 3 - reset decoder #3
+		 * 2 - reset decoder #2
+		 * 1 - reset decoder #1
+		 * 0 - reset decoder #0
+		 */
+		int compControl = reader.readUInt8();
+		resetDecoders(compControl);
+
+		int compType = compControl >> 4 & 0x0F;
+		switch (compType) {
+		case FILL_TYPE:
+			int color = renderer.readTightPixelColor(reader);
+			renderer.fillRect(color, rect);
+			break;
+		case JPEG_TYPE:
+            assert 3 == bytesPerPixel : "Tight doesn't support JPEG subencoding while depth not equal to 24bpp is used";
+			processJpegType(reader, renderer, rect);
+			break;
+		default:
+			assert compType <= JPEG_TYPE : "Compression control byte is incorrect!";
+			processBasicType(compControl, reader, renderer, rect);
+		}
+	}
+
+	private void processBasicType(int compControl, Reader reader,
+			Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
+		decoderId = (compControl & STREAM_ID_MASK) >> 4;
+
+		int filterId = 0;
+		if ((compControl & FILTER_ID_MASK) > 0) { // filter byte presence
+			filterId = reader.readUInt8();
+		}
+		int bytesPerCPixel = renderer.getBytesPerPixelTight();
+		int lengthCurrentbpp = bytesPerCPixel * rect.width * rect.height;
+		byte [] buffer;
+		switch (filterId) {
+		case BASIC_FILTER:
+			buffer = readTightData(lengthCurrentbpp, reader);
+			renderer.drawTightBytes(buffer, 0, rect.x, rect.y, rect.width, rect.height);
+			break;
+		case PALETTE_FILTER:
+			int paletteSize = reader.readUInt8() + 1;
+            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, paletteSize);
+			break;
+		case GRADIENT_FILTER:
+/*
+ * The "gradient" filter pre-processes pixel data with a simple algorithm
+ * which converts each color component to a difference between a "predicted"
+ * intensity and the actual intensity. Such a technique does not affect
+ * uncompressed data size, but helps to compress photo-like images better.
+ * Pseudo-code for converting intensities to differences is the following:
+ *
+ * 	P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1];
+ * 	if (P[i,j] < 0) then P[i,j] := 0;
+ * 	if (P[i,j] > MAX) then P[i,j] := MAX;
+ * 	D[i,j] := V[i,j] - P[i,j];
+ *
+ * Here V[i,j] is the intensity of a color component for a pixel at
+ * coordinates (i,j). MAX is the maximum value of intensity for a color
+ * component.*/
+			buffer = readTightData(bytesPerCPixel * rect.width * rect.height, reader);
+			byte [][] opRows = new byte[2][rect.width * 3 + 3];
+			int opRowIndex = 0;
+			byte [] components = new byte[3];
+			int pixelOffset = 0;
+			ColorDecoder colorDecoder = renderer.getColorDecoder();
+			for (int i = 0; i < rect.height; ++i) {
+				// exchange thisRow and prevRow:
+				byte [] thisRow = opRows[opRowIndex];
+				byte [] prevRow = opRows[opRowIndex = (opRowIndex + 1) % 2];
+				for (int j = 3; j < rect.width * 3 + 3; j += 3) {
+					colorDecoder.fillRawComponents(components, buffer, pixelOffset);
+					pixelOffset += bytesPerCPixel;
+					int
+					d = (0xff & prevRow[j + 0]) + // "upper" pixel (from prev row)
+						(0xff & thisRow[j + 0 - 3]) - // prev pixel
+						(0xff & prevRow[j + 0 - 3]); // "diagonal" prev pixel
+					thisRow[j + 0] = (byte) (components[0] + (d < 0 ? 0 : d > colorDecoder.redMax ? colorDecoder.redMax: d) & colorDecoder.redMax);
+					d = (0xff & prevRow[j + 1]) +
+						(0xff & thisRow[j + 1 - 3]) -
+						(0xff & prevRow[j + 1 - 3]);
+					thisRow[j + 1] = (byte) (components[1] + (d < 0 ? 0 : d > colorDecoder.greenMax ? colorDecoder.greenMax: d) & colorDecoder.greenMax);
+					d = (0xff & prevRow[j + 2]) +
+						(0xff & thisRow[j + 2 - 3]) -
+						(0xff & prevRow[j + 2 - 3]);
+					thisRow[j + 2] = (byte) (components[2] + (d < 0 ? 0 : d > colorDecoder.blueMax ? colorDecoder.blueMax: d) & colorDecoder.blueMax);
+				}
+				renderer.drawUncaliberedRGBLine(thisRow, rect.x, rect.y + i, rect.width);
+			}
+
+			break;
+		default:
+			break;
+		}
+	}
+
+	/**
+	 * Complete palette from reader
+	 */
+	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
+		 */
+        if (null == palette) palette = new int[256];
+		for (int i = 0; i < paletteSize; ++i) {
+			palette[i] = renderer.readTightPixelColor(reader);
+		}
+	}
+
+	/**
+	 * Reads compressed (expected length >= MIN_SIZE_TO_COMPRESS) or
+	 * uncompressed data. When compressed decompresses it.
+	 *
+	 * @param expectedLength expected data length in bytes
+	 * @param reader data source
+	 * @return result data
+	 * @throws TransportException
+	 */
+	private byte[] readTightData(int expectedLength, Reader reader) throws TransportException {
+		if (expectedLength < MIN_SIZE_TO_COMPRESS) {
+			byte [] buffer = ByteBuffer.getInstance().getBuffer(expectedLength);
+			reader.readBytes(buffer, 0, expectedLength);
+			return buffer;
+		} else
+			return readCompressedData(expectedLength, reader);
+	}
+
+	/**
+	 * Reads compressed data length, then read compressed data into rawBuffer
+     * and decompress data with expected length == length
+     *
+     * Note: returned data contains not only decompressed data but raw data at array tail
+     * which need to be ignored. Use only first expectedLength bytes.
+     *
+	 * @param expectedLength expected data length
+	 * @param reader data source
+	 * @return decompressed data (length == expectedLength) / + followed raw data (ignore, please)
+	 * @throws TransportException
+	 */
+	private byte[] readCompressedData(int expectedLength, Reader reader) throws TransportException {
+		int rawDataLength = readCompactSize(reader);
+
+		byte [] buffer = ByteBuffer.getInstance().getBuffer(expectedLength + rawDataLength);
+		// read compressed (raw) data behind space allocated for decompressed data
+		reader.readBytes(buffer, expectedLength, rawDataLength);
+		if (null == decoders[decoderId]) {
+			decoders[decoderId] = new Inflater();
+		}
+		Inflater decoder = decoders[decoderId];
+		decoder.setInput(buffer, expectedLength, rawDataLength);
+		try {
+			decoder.inflate(buffer, 0, expectedLength);
+		} catch (DataFormatException e) {
+			logger.throwing("TightDecoder", "readCompressedData", e);
+			throw new TransportException("cannot inflate tight compressed data", e);
+		}
+		return buffer;
+	}
+
+	private void processJpegType(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int jpegBufferLength = readCompactSize(reader);
+		byte [] bytes = ByteBuffer.getInstance().getBuffer(jpegBufferLength);
+		reader.readBytes(bytes, 0, jpegBufferLength);
+		renderer.drawJpegImage(bytes, 0, jpegBufferLength, rect);
+	}
+
+	/**
+	 * Read an integer from reader in compact representation (from 1 to 3 bytes).
+	 * Highest bit of read byte set to 1 means next byte contains data.
+	 * Lower 7 bit of each byte contains significant data. Max bytes = 3.
+	 * Less significant bytes first order.
+	 *
+	 * @param reader data source
+	 * @return int value
+	 * @throws TransportException
+	 */
+	private int readCompactSize(Reader reader) throws TransportException {
+		int b = reader.readUInt8();
+		int size = b & 0x7F;
+		if ((b & 0x80) != 0) {
+			b = reader.readUInt8();
+			size += (b & 0x7F) << 7;
+			if ((b & 0x80) != 0) {
+				size += reader.readUInt8() << 14;
+			}
+		}
+		return size;
+	}
+
+	/**
+	 * Flush (reset) zlib decoders when bits 3, 2, 1, 0 of compControl is set
+	 * @param compControl control flags
+	 */
+	private void resetDecoders(int compControl) {
+		for (int i=0; i < DECODERS_NUM; ++i) {
+			if ((compControl & 1) != 0 && decoders[i] != null) {
+				decoders[i].reset();
+			}
+			compControl >>= 1;
+		}
+
+	}
+
+	@Override
+	public void reset() {
+		decoders = new Inflater[DECODERS_NUM];
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java	Thu Sep 11 07:30:03 2014 +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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+public class ZRLEDecoder extends ZlibDecoder {
+	private static final int MAX_TILE_SIZE = 64;
+    private int[] decodedBitmap;
+    private int[] palette;
+
+    @Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int zippedLength = (int) reader.readUInt32();
+		if (0 == zippedLength) return;
+		int length = rect.width * rect.height * renderer.getBytesPerPixel();
+		byte[] bytes = unzip(reader, zippedLength, length);
+		int offset = zippedLength;
+		int maxX = rect.x + rect.width;
+		int maxY = rect.y + rect.height;
+        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 += 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, paletteSize);
+				if (1 == subencoding) { // A solid tile consisting of a single colour
+					renderer.fillRect(palette[0], tileX, tileY, tileWidth, tileHeight);
+					continue;
+				}
+				if (isRle) {
+					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, 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, 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.getBytesPerCPixel();
+		int decodedOffset = 0;
+		int decodedEnd = tileWidth * tileHeight;
+		int index = offset;
+		while (decodedOffset < decodedEnd) {
+			int color = renderer.getCompactPixelColor(bytes, index);
+			index += bytesPerCPixel;
+			int rlength = 1;
+			do {
+				rlength += bytes[index] & 0x0ff;
+			} while ((bytes[index++] & 0x0ff) == 255);
+			assert rlength <= decodedEnd - decodedOffset;
+			renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, rlength, color);
+			decodedOffset += rlength;
+		}
+		renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
+		return index - offset;
+	}
+
+	private int decodePaletteRle(byte[] bytes, int offset, Renderer renderer,
+                                 int tileX, int tileY, int tileWidth, int tileHeight) {
+		int decodedOffset = 0;
+		int decodedEnd = tileWidth * tileHeight;
+		int index = offset;
+		while (decodedOffset < decodedEnd) {
+			int colorIndex = bytes[index++];
+			int color = palette[colorIndex & 127];
+			int rlength = 1;
+			if ((colorIndex & 128) != 0) {
+				do {
+					rlength += bytes[index] & 0x0ff;
+				} while (bytes[index++] == (byte) 255);
+			}
+			assert rlength <= decodedEnd - decodedOffset;
+			renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, rlength, color);
+			decodedOffset += rlength;
+		}
+		renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
+		return index - offset;
+	}
+
+	private int decodePacked(byte[] bytes, int offset, Renderer renderer,
+                             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;
+		int decodedOffset = 0;
+		for (int i = 0; i < tileHeight; ++i) {
+			int decodedRowEnd = decodedOffset + tileWidth;
+			int byteProcessed = 0;
+			int bitsRemain = 0;
+
+			while (decodedOffset < decodedRowEnd) {
+				if (bitsRemain == 0) {
+					byteProcessed = bytes[packedOffset++];
+					bitsRemain = 8;
+				}
+				bitsRemain -= bitsPerPalletedPixel;
+				int index = byteProcessed >> bitsRemain & (1 << bitsPerPalletedPixel) - 1 & 127;
+				int color = palette[index];
+				renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, 1, color);
+				++decodedOffset;
+			}
+		}
+		renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
+		return packedOffset - offset;
+	}
+
+	private int decodeRaw(byte[] bytes, int offset, Renderer renderer,
+			int tileX, int tileY, int tileWidth, int tileHeight) throws TransportException {
+		return renderer.drawCompactBytes(bytes, offset, tileX, tileY, tileWidth, tileHeight);
+	}
+
+	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* bytesPerCPixel);
+		}
+		return paletteSize * bytesPerCPixel;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/encoding/decoder/ZlibDecoder.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,72 @@
+// 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.rfb.encoding.decoder;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.transport.Reader;
+
+import java.io.ByteArrayInputStream;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+public class ZlibDecoder extends Decoder {
+	private Inflater decoder;
+
+	@Override
+	public void decode(Reader reader, Renderer renderer,
+			FramebufferUpdateRectangle rect) throws TransportException {
+		int zippedLength = (int) reader.readUInt32();
+		if (0 == zippedLength) return;
+		int length = rect.width * rect.height * renderer.getBytesPerPixel();
+		byte[] bytes = unzip(reader, zippedLength, length);
+		Reader unzippedReader =
+			new Reader(
+					new ByteArrayInputStream(bytes, zippedLength, length));
+		RawDecoder.getInstance().decode(unzippedReader, renderer, rect);
+	}
+
+	protected byte[] unzip(Reader reader, int zippedLength, int length)
+			throws TransportException {
+		byte [] bytes = ByteBuffer.getInstance().getBuffer(zippedLength + length);
+		reader.readBytes(bytes, 0, zippedLength);
+		if (null == decoder) {
+			decoder = new Inflater();
+		}
+		decoder.setInput(bytes, 0, zippedLength);
+		try {
+			decoder.inflate(bytes, zippedLength, length);
+		} catch (DataFormatException e) {
+			throw new TransportException("cannot inflate Zlib data", e);
+		}
+		return bytes;
+	}
+
+	@Override
+	public void reset() {
+		decoder = null;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/LocalPointer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,34 @@
+// 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.rfb.protocol;
+
+/**
+ * How to track local mouse cursor.
+ */
+public enum LocalPointer {
+	ON,
+	OFF,
+	HIDE,
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/MessageQueue.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,58 @@
+// 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.rfb.protocol;
+
+import com.glavsoft.rfb.client.ClientToServerMessage;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class MessageQueue {
+	private final BlockingQueue<ClientToServerMessage> queue;
+
+	public MessageQueue() {
+		queue = new LinkedBlockingQueue<ClientToServerMessage>();
+	}
+
+	public void put(ClientToServerMessage message) {
+		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.poll(1, TimeUnit.SECONDS);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/Protocol.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,311 @@
+// 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.rfb.protocol;
+
+import com.glavsoft.core.SettingsChangedEvent;
+import com.glavsoft.exceptions.*;
+import com.glavsoft.rfb.*;
+import com.glavsoft.rfb.client.ClientToServerMessage;
+import com.glavsoft.rfb.client.FramebufferUpdateRequestMessage;
+import com.glavsoft.rfb.client.SetEncodingsMessage;
+import com.glavsoft.rfb.client.SetPixelFormatMessage;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.encoding.decoder.DecodersContainer;
+import com.glavsoft.rfb.protocol.state.HandshakeState;
+import com.glavsoft.rfb.protocol.state.ProtocolState;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+import java.util.logging.Logger;
+
+public class Protocol implements ProtocolContext, IChangeSettingsListener {
+	private ProtocolState state;
+	private final Logger logger;
+	private final IPasswordRetriever passwordRetriever;
+	private final ProtocolSettings settings;
+	private int fbWidth;
+	private int fbHeight;
+	private PixelFormat pixelFormat;
+	private final Reader reader;
+	private final Writer writer;
+	private String remoteDesktopName;
+	private MessageQueue messageQueue;
+	private final DecodersContainer decoders;
+	private SenderTask senderTask;
+	private ReceiverTask receiverTask;
+	private IRfbSessionListener rfbSessionListener;
+	private IRepaintController repaintController;
+	private PixelFormat serverPixelFormat;
+	private Thread senderThread;
+	private Thread receiverThread;
+    private boolean isTight;
+    private String protocolVersion;
+
+    public Protocol(Reader reader, Writer writer,
+			IPasswordRetriever passwordRetriever, ProtocolSettings settings) {
+		this.reader = reader;
+		this.writer = writer;
+		this.passwordRetriever = passwordRetriever;
+		this.settings = settings;
+		decoders = new DecodersContainer();
+		decoders.instantiateDecodersWhenNeeded(settings.encodings);
+		state = new HandshakeState(this);
+        logger = Logger.getLogger(getClass().getName());
+    }
+
+	@Override
+	public void changeStateTo(ProtocolState state) {
+		this.state = state;
+	}
+
+	public void handshake() throws UnsupportedProtocolVersionException, UnsupportedSecurityTypeException,
+			AuthenticationFailedException, TransportException, FatalException {
+		while (state.next()) {
+			// continue;
+		}
+		this.messageQueue = new MessageQueue();
+	}
+
+	@Override
+	public PixelFormat getPixelFormat() {
+		return pixelFormat;
+	}
+
+	@Override
+	public void setPixelFormat(PixelFormat pixelFormat) {
+		this.pixelFormat = pixelFormat;
+		if (repaintController != null) {
+			repaintController.setPixelFormat(pixelFormat);
+		}
+	}
+
+	@Override
+	public String getRemoteDesktopName() {
+		return remoteDesktopName;
+	}
+
+	@Override
+	public void setRemoteDesktopName(String name) {
+		remoteDesktopName = name;
+	}
+
+	@Override
+	public int getFbWidth() {
+		return fbWidth;
+	}
+
+	@Override
+	public void setFbWidth(int fbWidth) {
+		this.fbWidth = fbWidth;
+	}
+
+	@Override
+	public int getFbHeight() {
+		return fbHeight;
+	}
+
+	@Override
+	public void setFbHeight(int fbHeight) {
+		this.fbHeight = fbHeight;
+	}
+
+	@Override
+	public IPasswordRetriever getPasswordRetriever() {
+		return passwordRetriever;
+	}
+
+	@Override
+	public ProtocolSettings getSettings() {
+		return settings;
+	}
+
+    @Override
+	public Writer getWriter() {
+		return writer;
+	}
+
+	@Override
+	public Reader getReader() {
+		return reader;
+	}
+
+	/**
+	 * Following the server initialisation message it's up to the client to send
+	 * whichever protocol messages it wants.  Typically it will send a
+	 * SetPixelFormat message and a SetEncodings message, followed by a
+	 * FramebufferUpdateRequest.  From then on the server will send
+	 * FramebufferUpdate messages in response to the client's
+	 * FramebufferUpdateRequest messages.  The client should send
+	 * FramebufferUpdateRequest messages with incremental set to true when it has
+	 * finished processing one FramebufferUpdate and is ready to process another.
+	 * With a fast client, the rate at which FramebufferUpdateRequests are sent
+	 * should be regulated to avoid hogging the network.
+	 */
+	public void startNormalHandling(IRfbSessionListener rfbSessionListener,
+			IRepaintController repaintController, ClipboardController clipboardController) {
+		this.rfbSessionListener = rfbSessionListener;
+		this.repaintController = repaintController;
+//		if (settings.getColorDepth() == 0) {
+//			settings.setColorDepth(pixelFormat.depth); // the same the server sent when not initialized yet
+//		}
+		serverPixelFormat = pixelFormat;
+        correctServerPixelFormat();
+		setPixelFormat(createPixelFormat(settings));
+		sendMessage(new SetPixelFormatMessage(pixelFormat));
+		logger.fine("sent: " + pixelFormat);
+
+		sendSupportedEncodingsMessage(settings);
+		settings.addListener(this); // to support pixel format (color depth), and encodings changes
+		settings.addListener(repaintController);
+
+		sendRefreshMessage();
+		senderTask = new SenderTask(messageQueue, writer, this);
+		senderThread = new Thread(senderTask, "RfbSenderTask");
+		senderThread.start();
+		decoders.resetDecoders();
+		receiverTask = new ReceiverTask(
+				reader, repaintController,
+				clipboardController,
+				decoders, this);
+		receiverThread = new Thread(receiverTask, "RfbReceiverTask");
+		receiverThread.start();
+	}
+
+    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);
+	}
+
+	private void sendSupportedEncodingsMessage(ProtocolSettings settings) {
+		decoders.instantiateDecodersWhenNeeded(settings.encodings);
+		SetEncodingsMessage encodingsMessage = new SetEncodingsMessage(settings.encodings);
+		sendMessage(encodingsMessage);
+		logger.fine("sent: " + encodingsMessage.toString());
+	}
+
+	/**
+	 * create pixel format by bpp
+	 */
+	private PixelFormat createPixelFormat(ProtocolSettings settings) {
+		int serverBigEndianFlag = serverPixelFormat.bigEndianFlag;
+		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.COLOR_DEPTH_SERVER_SETTINGS:
+			return serverPixelFormat;
+		default:
+			// unsupported bpp, use default
+			return PixelFormat.create24bitColorDepthPixelFormat(serverBigEndianFlag);
+		}
+	}
+
+	@Override
+	public void settingsChanged(SettingsChangedEvent e) {
+		ProtocolSettings settings = (ProtocolSettings) e.getSource();
+		if (settings.isChangedEncodings()) {
+			sendSupportedEncodingsMessage(settings);
+		}
+		if (settings.isChangedColorDepth() && receiverTask != null) {
+			receiverTask.queueUpdatePixelFormat(createPixelFormat(settings));
+		}
+	}
+
+	@Override
+	public void sendRefreshMessage() {
+		sendMessage(new FramebufferUpdateRequestMessage(0, 0, fbWidth, fbHeight, false));
+		logger.fine("sent: full FB Refresh");
+	}
+
+	@Override
+	public void cleanUpSession(String message) {
+		cleanUpSession();
+		rfbSessionListener.rfbSessionStopped(message);
+	}
+
+	public synchronized void cleanUpSession() {
+		if (senderTask != null) { senderTask.stopTask(); }
+		if (receiverTask != null) { receiverTask.stopTask(); }
+		if (senderTask != null) {
+			try {
+				senderThread.join(1000);
+			} catch (InterruptedException e) {
+				// nop
+			}
+			senderTask = null;
+		}
+		if (receiverTask != null) {
+			try {
+				receiverThread.join(1000);
+			} catch (InterruptedException e) {
+				// nop
+			}
+			receiverTask = null;
+		}
+	}
+
+    @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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/ProtocolContext.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,72 @@
+// 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.rfb.protocol;
+
+import com.glavsoft.rfb.IPasswordRetriever;
+import com.glavsoft.rfb.client.ClientToServerMessage;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.protocol.state.ProtocolState;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+import java.util.logging.Logger;
+
+public interface ProtocolContext {
+
+	void changeStateTo(ProtocolState state);
+
+	IPasswordRetriever getPasswordRetriever();
+
+	ProtocolSettings getSettings();
+
+    Writer getWriter();
+	Reader getReader();
+
+	int getFbWidth();
+	void setFbWidth(int frameBufferWidth);
+
+	int getFbHeight();
+	void setFbHeight(int frameBufferHeight);
+
+	PixelFormat getPixelFormat();
+	void setPixelFormat(PixelFormat pixelFormat);
+
+	void setRemoteDesktopName(String name);
+
+	void sendMessage(ClientToServerMessage message);
+
+	String getRemoteDesktopName();
+
+	void sendRefreshMessage();
+	
+	void cleanUpSession(String message);
+
+    void setTight(boolean isTight);
+	boolean isTight();
+
+    void setProtocolVersion(String protocolVersion);
+    String getProtocolVersion();
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/ProtocolSettings.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,445 @@
+// 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.rfb.protocol;
+
+import com.glavsoft.core.SettingsChangedEvent;
+import com.glavsoft.rfb.CapabilityContainer;
+import com.glavsoft.rfb.IChangeSettingsListener;
+import com.glavsoft.rfb.RfbCapabilityInfo;
+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;
+import java.util.List;
+
+/**
+ * Protocol Settings class
+ */
+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;
+
+	// 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 COLOR_DEPTH_SERVER_SETTINGS = 0;
+
+	private static final int DEFAULT_COLOR_DEPTH = COLOR_DEPTH_24;
+
+	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;
+	public static final int CHANGED_MOUSE_CURSOR_TRACK          = 1 << 4;
+	public static final int CHANGED_COMPRESSION_LEVEL           = 1 << 5;
+	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_COLOR_DEPTH = 1 << 9;
+    public static final int CHANGED_SHARED                      = 1 << 10;
+
+	private transient int changedSettingsMask;
+
+    private boolean sharedFlag;
+    private boolean viewOnly;
+    private EncodingType preferredEncoding;
+    private boolean allowCopyRect;
+    private boolean showRemoteCursor;
+    private LocalPointer mouseCursorTrack;
+    private int compressionLevel;
+    private int jpegQuality;
+	private boolean allowClipboardTransfer;
+	private boolean convertToAscii;
+	private int colorDepth;
+
+	public transient LinkedHashSet<EncodingType> encodings;
+	private transient final List<IChangeSettingsListener> listeners;
+
+    public transient CapabilityContainer
+		tunnelingCapabilities,
+		authCapabilities,
+		serverMessagesCapabilities,
+		clientMessagesCapabilities,
+		encodingTypesCapabilities;
+	private transient String remoteCharsetName;
+
+	public static ProtocolSettings getDefaultSettings() {
+    	ProtocolSettings settings = new ProtocolSettings();
+	    settings.initKnownAuthCapabilities(settings.authCapabilities);
+	    settings.initKnownEncodingTypesCapabilities(settings.encodingTypesCapabilities);
+        return settings;
+    }
+
+	private ProtocolSettings() {
+        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;
+   	}
+
+	public ProtocolSettings(ProtocolSettings s) {
+		this();
+        copyDataFrom(s);
+        changedSettingsMask = s.changedSettingsMask;
+        encodings = s.encodings;
+    }
+
+    public void copyDataFrom(ProtocolSettings s) {
+        copyDataFrom(s, 0);
+    }
+
+    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);
+
+        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);
+    }
+
+    private void initKnownAuthCapabilities(CapabilityContainer cc) {
+		cc.addEnabled(SecurityType.NONE_AUTHENTICATION.getId(),
+				RfbCapabilityInfo.VENDOR_STANDARD, RfbCapabilityInfo.AUTHENTICATION_NO_AUTH);
+		cc.addEnabled(SecurityType.VNC_AUTHENTICATION.getId(),
+				RfbCapabilityInfo.VENDOR_STANDARD, RfbCapabilityInfo.AUTHENTICATION_VNC_AUTH);
+	    //cc.addEnabled( 19, "VENC", "VENCRYPT");
+	    //cc.addEnabled( 20, "GTKV", "SASL____");
+	    //cc.addEnabled(129, RfbCapabilityInfo.TIGHT_VNC_VENDOR, "ULGNAUTH");
+	    //cc.addEnabled(130, RfbCapabilityInfo.TIGHT_VNC_VENDOR, "XTRNAUTH");
+	}
+
+	private void initKnownEncodingTypesCapabilities(CapabilityContainer cc) {
+		cc.add(EncodingType.COPY_RECT.getId(),
+				RfbCapabilityInfo.VENDOR_STANDARD, RfbCapabilityInfo.ENCODING_COPYRECT);
+		cc.add(EncodingType.HEXTILE.getId(),
+				RfbCapabilityInfo.VENDOR_STANDARD, RfbCapabilityInfo.ENCODING_HEXTILE);
+		cc.add(EncodingType.ZLIB.getId(),
+				RfbCapabilityInfo.VENDOR_TRIADA, RfbCapabilityInfo.ENCODING_ZLIB);
+		cc.add(EncodingType.ZRLE.getId(),
+				RfbCapabilityInfo.VENDOR_TRIADA, RfbCapabilityInfo.ENCODING_ZRLE);
+		cc.add(EncodingType.RRE.getId(),
+				RfbCapabilityInfo.VENDOR_STANDARD, RfbCapabilityInfo.ENCODING_RRE);
+		cc.add(EncodingType.TIGHT.getId(),
+				RfbCapabilityInfo.VENDOR_TIGHT, RfbCapabilityInfo.ENCODING_TIGHT);
+
+		cc.add(EncodingType.RICH_CURSOR.getId(),
+				RfbCapabilityInfo.VENDOR_TIGHT, RfbCapabilityInfo.ENCODING_RICH_CURSOR);
+		cc.add(EncodingType.CURSOR_POS.getId(),
+				RfbCapabilityInfo.VENDOR_TIGHT, RfbCapabilityInfo.ENCODING_CURSOR_POS);
+		cc.add(EncodingType.DESKTOP_SIZE.getId(),
+				RfbCapabilityInfo.VENDOR_TIGHT, RfbCapabilityInfo.ENCODING_DESKTOP_SIZE);
+	}
+
+	public void addListener(IChangeSettingsListener listener) {
+		listeners.add(listener);
+	}
+
+	public byte getSharedFlag() {
+		return (byte) (sharedFlag ? 1 : 0);
+	}
+
+	public boolean isShared() {
+		return sharedFlag;
+	}
+
+	public void setSharedFlag(boolean sharedFlag) {
+        if (this.sharedFlag != sharedFlag) {
+		    this.sharedFlag = sharedFlag;
+            changedSettingsMask |= CHANGED_SHARED;
+        }
+	}
+
+	public boolean isViewOnly() {
+		return viewOnly;
+	}
+
+	public void setViewOnly(boolean viewOnly) {
+		if (this.viewOnly != viewOnly) {
+			this.viewOnly = viewOnly;
+			changedSettingsMask |= CHANGED_VIEW_ONLY;
+		}
+	}
+
+	public void enableAllEncodingCaps() {
+		encodingTypesCapabilities.setAllEnable(true);
+
+	}
+
+	public int getColorDepth() {
+		return colorDepth;
+	}
+
+	/**
+	 * Set depth only in 3, 6, 8, 16, 32. When depth is wrong, it resets to {@link #DEFAULT_COLOR_DEPTH}
+	 */
+	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:
+				colorDepth = DEFAULT_COLOR_DEPTH;
+			}
+			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
+		} else {
+			encodings.add(preferredEncoding); // preferred first
+			encodings.addAll(EncodingType.ordinaryEncodings);
+			if (compressionLevel > 0 && compressionLevel < 10) {
+				encodings.add(EncodingType.byId(
+						EncodingType.COMPRESS_LEVEL_0.getId() + compressionLevel));
+			}
+			if (jpegQuality > 0 && jpegQuality < 10 &&
+					(colorDepth == COLOR_DEPTH_24 || colorDepth == COLOR_DEPTH_SERVER_SETTINGS)) {
+				encodings.add(EncodingType.byId(
+						EncodingType.JPEG_QUALITY_LEVEL_0.getId() + jpegQuality));
+			}
+			if (allowCopyRect) {
+				encodings.add(EncodingType.COPY_RECT);
+			}
+		}
+		switch(mouseCursorTrack) {
+		case OFF:
+			setShowRemoteCursor(false);
+			break;
+		case HIDE:
+			setShowRemoteCursor(false);
+			encodings.add(EncodingType.RICH_CURSOR);
+			encodings.add(EncodingType.CURSOR_POS);
+			break;
+		case ON:
+		default:
+			setShowRemoteCursor(true);
+			encodings.add(EncodingType.RICH_CURSOR);
+			encodings.add(EncodingType.CURSOR_POS);
+		}
+		encodings.add(EncodingType.DESKTOP_SIZE);
+		if ( isEncodingsChanged(this.encodings, encodings) || isChangedEncodings()) {
+			this.encodings = encodings;
+			changedSettingsMask |= CHANGED_ENCODINGS;
+		}
+	}
+
+	private boolean isEncodingsChanged(LinkedHashSet<EncodingType> encodings1, LinkedHashSet<EncodingType> encodings2) {
+		if (null == encodings1 || encodings1.size() != encodings2.size()) return true;
+		Iterator<EncodingType> it1 = encodings1.iterator();
+		Iterator<EncodingType> it2 = encodings2.iterator();
+		while (it1.hasNext()) {
+			EncodingType v1 = it1.next();
+			EncodingType v2 = it2.next();
+			if (v1 != v2) return true;
+		}
+		return false;
+	}
+
+	public void fireListeners() {
+        if (null == listeners) return;
+        final SettingsChangedEvent event = new SettingsChangedEvent(new ProtocolSettings(this));
+		changedSettingsMask = 0;
+		for (IChangeSettingsListener listener : listeners) {
+			listener.settingsChanged(event);
+		}
+	}
+
+	public static boolean isRfbSettingsChangedFired(SettingsChangedEvent event) {
+		return event.getSource() instanceof ProtocolSettings;
+	}
+
+	public void setPreferredEncoding(EncodingType preferredEncoding) {
+		if (this.preferredEncoding != preferredEncoding) {
+			this.preferredEncoding = preferredEncoding;
+			changedSettingsMask |= CHANGED_ENCODINGS;
+			refine();
+		}
+	}
+
+	public EncodingType getPreferredEncoding() {
+		return preferredEncoding;
+	}
+
+	public void setAllowCopyRect(boolean allowCopyRect) {
+		if (this.allowCopyRect != allowCopyRect) {
+			this.allowCopyRect = allowCopyRect;
+			changedSettingsMask |= CHANGED_ALLOW_COPY_RECT;
+			refine();
+		}
+	}
+
+	public boolean isAllowCopyRect() {
+		return allowCopyRect;
+	}
+
+	private void setShowRemoteCursor(boolean showRemoteCursor) {
+		if (this.showRemoteCursor != showRemoteCursor) {
+			this.showRemoteCursor = showRemoteCursor;
+			changedSettingsMask |= CHANGED_SHOW_REMOTE_CURSOR;
+		}
+	}
+
+	public boolean isShowRemoteCursor() {
+		return showRemoteCursor;
+	}
+
+	public void setMouseCursorTrack(LocalPointer mouseCursorTrack) {
+		if (this.mouseCursorTrack != mouseCursorTrack) {
+			this.mouseCursorTrack = mouseCursorTrack;
+			changedSettingsMask |= CHANGED_MOUSE_CURSOR_TRACK;
+			refine();
+		}
+	}
+
+	public LocalPointer getMouseCursorTrack() {
+		return mouseCursorTrack;
+	}
+
+	public void setCompressionLevel(int compressionLevel) {
+		if (this.compressionLevel != compressionLevel) {
+			this.compressionLevel = compressionLevel;
+			changedSettingsMask |= CHANGED_COMPRESSION_LEVEL;
+			refine();
+		}
+	}
+
+	public int getCompressionLevel() {
+		return compressionLevel;
+	}
+
+	public void setJpegQuality(int jpegQuality) {
+		if (this.jpegQuality != jpegQuality) {
+			this.jpegQuality = jpegQuality;
+			changedSettingsMask |= CHANGED_JPEG_QUALITY;
+			refine();
+		}
+	}
+
+	public int getJpegQuality() {
+		return jpegQuality;
+	}
+
+	public void setAllowClipboardTransfer(boolean enable) {
+		if (this.allowClipboardTransfer != enable) {
+			this.allowClipboardTransfer = enable;
+			changedSettingsMask |= CHANGED_ALLOW_CLIPBOARD_TRANSFER;
+		}
+	}
+
+	public boolean isAllowClipboardTransfer() {
+		return allowClipboardTransfer;
+	}
+
+	public boolean isConvertToAscii() {
+		return convertToAscii;
+	}
+
+	public void setConvertToAscii(boolean convertToAscii) {
+		if (this.convertToAscii != convertToAscii) {
+			this.convertToAscii = convertToAscii;
+			changedSettingsMask |= CHANGED_CONVERT_TO_ASCII;
+		}
+	}
+
+	public boolean isChangedEncodings() {
+		return (changedSettingsMask & CHANGED_ENCODINGS) == CHANGED_ENCODINGS;
+	}
+
+	public boolean isChangedColorDepth() {
+		return (changedSettingsMask & CHANGED_COLOR_DEPTH) == CHANGED_COLOR_DEPTH;
+	}
+
+	public void setRemoteCharsetName(String remoteCharsetName) {
+		this.remoteCharsetName = remoteCharsetName;
+	}
+
+	public  String getRemoteCharsetName() {
+		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 +
+                '}';
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/ReceiverTask.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,209 @@
+// 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.rfb.protocol;
+
+import com.glavsoft.drawing.Renderer;
+import com.glavsoft.exceptions.CommonException;
+import com.glavsoft.exceptions.ProtocolException;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.ClipboardController;
+import com.glavsoft.rfb.IRepaintController;
+import com.glavsoft.rfb.client.FramebufferUpdateRequestMessage;
+import com.glavsoft.rfb.client.SetPixelFormatMessage;
+import com.glavsoft.rfb.encoding.EncodingType;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.encoding.decoder.Decoder;
+import com.glavsoft.rfb.encoding.decoder.DecodersContainer;
+import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
+import com.glavsoft.rfb.encoding.decoder.RichCursorDecoder;
+import com.glavsoft.transport.Reader;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.logging.Logger;
+
+public class ReceiverTask implements Runnable {
+	private static final byte FRAMEBUFFER_UPDATE = 0;
+	private static final byte SET_COLOR_MAP_ENTRIES = 1;
+	private static final byte BELL = 2;
+	private static final byte SERVER_CUT_TEXT = 3;
+
+
+	private static Logger logger = Logger.getLogger("com.glavsoft.rfb.protocol.ReceiverTask");
+	private final Reader reader;
+	private volatile boolean isRunning = false;
+	private Renderer renderer;
+	private final IRepaintController repaintController;
+	private final ClipboardController clipboardController;
+	private final DecodersContainer decoders;
+	private FramebufferUpdateRequestMessage fullscreenFbUpdateIncrementalRequest;
+	private final ProtocolContext context;
+	private PixelFormat pixelFormat;
+	private boolean needSendPixelFormat;
+
+	public ReceiverTask(Reader reader,
+	                    IRepaintController repaintController, ClipboardController clipboardController,
+	                    DecodersContainer decoders, ProtocolContext context) {
+		this.reader = reader;
+		this.repaintController = repaintController;
+		this.clipboardController = clipboardController;
+		this.context = context;
+		this.decoders = decoders;
+		renderer = repaintController.createRenderer(reader, context.getFbWidth(), context.getFbHeight(),
+				context.getPixelFormat());
+		fullscreenFbUpdateIncrementalRequest =
+			new FramebufferUpdateRequestMessage(0, 0, context.getFbWidth(), context.getFbHeight(), true);
+	}
+
+	@Override
+	public void run() {
+		isRunning = true;
+		while (isRunning) {
+			try {
+				byte messageId = reader.readByte();
+				switch (messageId) {
+				case FRAMEBUFFER_UPDATE:
+//					logger.fine("Server message: FramebufferUpdate (0)");
+					framebufferUpdateMessage();
+					break;
+				case SET_COLOR_MAP_ENTRIES:
+					logger.severe("Server message SetColorMapEntries is not implemented. Skip.");
+					setColorMapEntries();
+					break;
+				case BELL:
+					logger.fine("Server message: Bell");
+					System.out.print("\0007");
+				    System.out.flush();
+					break;
+				case SERVER_CUT_TEXT:
+					logger.fine("Server message: CutText (3)");
+					serverCutText();
+					break;
+				default:
+					logger.severe("Unsupported server message. Id = " + messageId);
+				}
+			} catch (TransportException e) {
+				if (isRunning) {
+                    logger.severe("Close session: " + e.getMessage());
+					context.cleanUpSession("Connection closed.");
+				}
+				stopTask();
+			} catch (ProtocolException e) {
+				logger.severe(e.getMessage());
+				if (isRunning) {
+					context.cleanUpSession(e.getMessage() + "\nConnection closed.");
+				}
+				stopTask();
+			} catch (CommonException e) {
+				logger.severe(e.getMessage());
+				if (isRunning) {
+					context.cleanUpSession("Connection closed..");
+				}
+				stopTask();
+			} catch (Throwable te) {
+				StringWriter sw = new StringWriter();
+				PrintWriter pw = new PrintWriter(sw);
+				te.printStackTrace(pw);
+				if (isRunning) {
+					context.cleanUpSession(te.getMessage() + "\n" + sw.toString());
+				}
+				stopTask();
+			}
+		}
+	}
+
+	private void setColorMapEntries() throws TransportException {
+		reader.readByte();  // padding
+		reader.readUInt16(); // first color index
+		int length = reader.readUInt16();
+		while (length-- > 0) {
+			reader.readUInt16(); // R
+			reader.readUInt16(); // G
+			reader.readUInt16(); // B
+		}
+	}
+
+	private void serverCutText() throws TransportException {
+		reader.readByte();  // padding
+		reader.readInt16(); // padding
+		int length = reader.readInt32() & Integer.MAX_VALUE;
+		clipboardController.updateSystemClipboard(reader.readBytes(length));
+	}
+
+	public void framebufferUpdateMessage() throws CommonException {
+		reader.readByte(); // padding
+		int numberOfRectangles = reader.readUInt16();
+		while (numberOfRectangles-- > 0) {
+			FramebufferUpdateRectangle rect = new FramebufferUpdateRectangle();
+			rect.fill(reader);
+
+			Decoder decoder = decoders.getDecoderByType(rect.getEncodingType());
+			logger.finest(rect.toString() + (0 == numberOfRectangles ? "\n---" : ""));
+			if (decoder != null) {
+				decoder.decode(reader, renderer, rect);
+				repaintController.repaintBitmap(rect);
+			} else if (rect.getEncodingType() == EncodingType.RICH_CURSOR) {
+				RichCursorDecoder.getInstance().decode(reader, renderer, rect);
+				repaintController.repaintCursor();
+			} else if (rect.getEncodingType() == EncodingType.CURSOR_POS) {
+				renderer.decodeCursorPosition(rect);
+				repaintController.repaintCursor();
+			} else if (rect.getEncodingType() == EncodingType.DESKTOP_SIZE) {
+				fullscreenFbUpdateIncrementalRequest =
+					new FramebufferUpdateRequestMessage(0, 0, rect.width, rect.height, true);
+				synchronized (renderer.getLock()) {
+					renderer = repaintController.createRenderer(reader, rect.width, rect.height,
+							context.getPixelFormat());
+				}
+				context.sendMessage(new FramebufferUpdateRequestMessage(0, 0, rect.width, rect.height, false));
+//				repaintController.repaintCursor();
+			} else
+				throw new CommonException("Unprocessed encoding: " + rect.toString());
+		}
+		synchronized (this) {
+			if (needSendPixelFormat) {
+				needSendPixelFormat = false;
+				context.setPixelFormat(pixelFormat);
+				context.sendMessage(new SetPixelFormatMessage(pixelFormat));
+				logger.fine("sent: "+pixelFormat);
+				context.sendRefreshMessage();
+				logger.fine("sent: nonincremental fb update");
+			} else {
+				context.sendMessage(fullscreenFbUpdateIncrementalRequest);
+			}
+		}
+	}
+
+	public synchronized void queueUpdatePixelFormat(PixelFormat pf) {
+		pixelFormat = pf;
+		needSendPixelFormat = true;
+//		context.sendMessage(new FramebufferUpdateRequestMessage(0, 0, 1, 1, false));
+	}
+
+	public void stopTask() {
+		isRunning = false;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/SenderTask.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,91 @@
+// 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.rfb.protocol;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.client.ClientToServerMessage;
+import com.glavsoft.transport.Writer;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.logging.Logger;
+
+public class SenderTask implements Runnable {
+
+	private final MessageQueue queue;
+	private final Writer writer;
+	private final ProtocolContext protocolContext;
+	private volatile boolean isRunning = false;
+
+	/**
+	 * Create sender task
+	 * Task runs as thread, receive messages from queue and sends them to writer.
+	 * When no messages appears in queue longer than timeout period, sends FramebufferUpdate
+	 * request
+	 * @param messageQueue queue to poll messages
+	 * @param writer writer to send messages out
+	 * @param protocolContext protocol
+	 */
+	public SenderTask(MessageQueue messageQueue, Writer writer, ProtocolContext protocolContext) {
+		this.queue = messageQueue;
+		this.writer = writer;
+		this.protocolContext = protocolContext;
+	}
+
+	@Override
+	public void run() {
+		isRunning = true;
+		while (isRunning) {
+			ClientToServerMessage message;
+			try {
+				message = queue.get();
+				if (message != null) {
+					message.send(writer);
+				}
+			} catch (InterruptedException e) {
+				// nop
+			} catch (TransportException e) {
+				Logger.getLogger(getClass().getName()).severe("Close session: " + e.getMessage());
+				if (isRunning) {
+					protocolContext.cleanUpSession("Connection closed");
+				}
+				stopTask();
+			} catch (Throwable te) {
+				StringWriter sw = new StringWriter();
+				PrintWriter pw = new PrintWriter(sw);
+				te.printStackTrace(pw);
+				if (isRunning) {
+					protocolContext.cleanUpSession(te.getMessage() + "\n" + sw.toString());
+				}
+				stopTask();
+			}
+		}
+	}
+
+	public void stopTask() {
+		isRunning = false;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/AuthHandler.java	Thu Sep 11 07:30:03 2014 +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.rfb.protocol.auth;
+
+import com.glavsoft.exceptions.FatalException;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+import com.glavsoft.rfb.CapabilityContainer;
+import com.glavsoft.rfb.IPasswordRetriever;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+public abstract class AuthHandler {
+	/**
+	 * Authenticate using apropriate auth scheme
+	 * @param reader
+	 * @param writer
+	 * @param passwordRetriever interface that realise callback function for password retrieving,
+	 * ex. by asking user with dialog frame etc.
+	 * @param authCaps authentication capabilities
+	 *
+	 * @return true if there was Tight protocol extention used, false - in the other way
+	 * @throws TransportException
+	 * @throws FatalException
+	 * @throws UnsupportedSecurityTypeException
+	 */
+	public abstract boolean authenticate(Reader reader, Writer writer,
+			CapabilityContainer authCaps, IPasswordRetriever passwordRetriever)
+		throws TransportException, FatalException, UnsupportedSecurityTypeException;
+	protected boolean useSecurityResult = true;
+	public abstract SecurityType getType();
+	public int getId() {
+		return getType().getId();
+	}
+	public String getName() {
+		return getType().name();
+	}
+	public boolean useSecurityResult() {
+		return useSecurityResult;
+	}
+	public void setUseSecurityResult(boolean enabled) {
+		useSecurityResult = enabled;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/NoneAuthentication.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,45 @@
+// 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.rfb.protocol.auth;
+
+import com.glavsoft.rfb.CapabilityContainer;
+import com.glavsoft.rfb.IPasswordRetriever;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+public class NoneAuthentication extends AuthHandler {
+
+	@Override
+	public boolean authenticate(Reader reader, Writer writer,
+			CapabilityContainer authCaps, IPasswordRetriever passwordRetriever) {
+		return false;
+	}
+
+	@Override
+	public SecurityType getType() {
+		return SecurityType.NONE_AUTHENTICATION;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/SecurityType.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,72 @@
+// 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.rfb.protocol.auth;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+
+/**
+ * Security types that implemented
+ */
+public enum SecurityType {
+	NONE_AUTHENTICATION(1),
+	VNC_AUTHENTICATION(2),
+//	int RA2_AUTHENTICATION = 5;
+//	int RA2NE_AUTHENTICATION = 6;
+	TIGHT_AUTHENTICATION(16);
+//	int ULTRA_AUTHENTICATION = 17;
+//	int TLS_AUTHENTICATION = 18;
+//	int VENCRYPT_AUTHENTICATION = 19;
+
+	private int id;
+	private SecurityType(int id) {
+		this.id = id;
+	}
+
+	public int getId() {
+		return id;
+	}
+
+	@SuppressWarnings("serial")
+	public
+	static Map<Integer, AuthHandler> implementedSecurityTypes =
+		new LinkedHashMap<Integer, AuthHandler>() {{
+			put(TIGHT_AUTHENTICATION.getId(), new TightAuthentication());
+			put(VNC_AUTHENTICATION.getId(), new VncAuthentication());
+			put(NONE_AUTHENTICATION.getId(), new NoneAuthentication());
+	}};
+
+	public static AuthHandler getAuthHandlerById(int id) throws UnsupportedSecurityTypeException {
+		AuthHandler typeSelected = null;
+		typeSelected = implementedSecurityTypes.get(id);
+		if (null == typeSelected) {
+			throw new UnsupportedSecurityTypeException("Not supported: " + id);
+		}
+		return typeSelected;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/TightAuthentication.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,153 @@
+// 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.rfb.protocol.auth;
+
+import java.util.logging.Logger;
+
+import com.glavsoft.exceptions.FatalException;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+import com.glavsoft.rfb.CapabilityContainer;
+import com.glavsoft.rfb.IPasswordRetriever;
+import com.glavsoft.rfb.RfbCapabilityInfo;
+import com.glavsoft.rfb.protocol.state.SecurityTypeState;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+/**
+ *
+ */
+public class TightAuthentication extends AuthHandler {
+
+	@Override
+	public SecurityType getType() {
+		return SecurityType.TIGHT_AUTHENTICATION;
+	}
+
+	@Override
+	public boolean authenticate(Reader reader, Writer writer,
+			CapabilityContainer authCaps, IPasswordRetriever passwordRetriever)
+	throws TransportException, FatalException, UnsupportedSecurityTypeException {
+	      initTunnelling(reader, writer);
+	      initAuthorization(reader, writer, authCaps, passwordRetriever);
+	      return true;
+	}
+
+	/**
+	 * Negotiation of Tunneling Capabilities (protocol versions 3.7t, 3.8t)
+	 *
+	 * If the chosen security type is rfbSecTypeTight, the server sends a list of
+	 * supported tunneling methods ("tunneling" refers to any additional layer of
+	 * data transformation, such as encryption or external compression.)
+	 *
+	 * nTunnelTypes specifies the number of following rfbCapabilityInfo structures
+	 * that list all supported tunneling methods in the order of preference.
+	 *
+	 * NOTE: If nTunnelTypes is 0, that tells the client that no tunneling can be
+	 * used, and the client should not send a response requesting a tunneling
+	 * method.
+	 *
+	 * typedef struct _rfbTunnelingCapsMsg {
+	 *     CARD32 nTunnelTypes;
+	 *     //followed by nTunnelTypes * rfbCapabilityInfo structures
+	 *  } rfbTunnelingCapsMsg;
+	 * #define sz_rfbTunnelingCapsMsg 4
+	 * ----------------------------------------------------------------------------
+	 * Tunneling Method Request (protocol versions 3.7t, 3.8t)
+	 *
+	 * If the list of tunneling capabilities sent by the server was not empty, the
+	 * client should reply with a 32-bit code specifying a particular tunneling
+	 * method.  The following code should be used for no tunneling.
+	 *
+	 * #define rfbNoTunneling 0
+	 * #define sig_rfbNoTunneling "NOTUNNEL"
+	 *
+	 */
+	private void initTunnelling(Reader reader, Writer writer)
+			throws TransportException {
+		long tunnelsCount;
+		tunnelsCount = reader.readUInt32();
+		if (tunnelsCount > 0) {
+			for (int i = 0; i < tunnelsCount; ++i) {
+				RfbCapabilityInfo rfbCapabilityInfo = new RfbCapabilityInfo(reader);
+				Logger.getLogger("com.glavsoft.rfb.protocol.auth").fine(rfbCapabilityInfo.toString());
+			}
+			writer.writeInt32(0); // NOTUNNEL
+		}
+	}
+
+	/**
+	 * Negotiation of Authentication Capabilities (protocol versions 3.7t, 3.8t)
+	 *
+	 * After setting up tunneling, the server sends a list of supported
+	 * authentication schemes.
+	 *
+	 * nAuthTypes specifies the number of following rfbCapabilityInfo structures
+	 * that list all supported authentication schemes in the order of preference.
+	 *
+	 * NOTE: If nAuthTypes is 0, that tells the client that no authentication is
+	 * necessary, and the client should not send a response requesting an
+	 * authentication scheme.
+	 *
+	 * typedef struct _rfbAuthenticationCapsMsg {
+	 *     CARD32 nAuthTypes;
+	 *     // followed by nAuthTypes * rfbCapabilityInfo structures
+	 * } rfbAuthenticationCapsMsg;
+	 * #define sz_rfbAuthenticationCapsMsg 4
+	 * @param authCaps TODO
+	 * @param passwordRetriever
+	 * @throws UnsupportedSecurityTypeException
+	 * @throws TransportException
+	 * @throws FatalException
+	 */
+	private void initAuthorization(Reader reader, Writer writer,
+			CapabilityContainer authCaps, IPasswordRetriever passwordRetriever)
+	throws UnsupportedSecurityTypeException, TransportException, FatalException {
+		int authCount;
+		authCount = reader.readInt32();
+		byte[] cap = new byte[authCount];
+		for (int i = 0; i < authCount; ++i) {
+			RfbCapabilityInfo rfbCapabilityInfo = new RfbCapabilityInfo(reader);
+			cap[i] = (byte) rfbCapabilityInfo.getCode();
+			Logger.getLogger("com.glavsoft.rfb.protocol.auth").fine(rfbCapabilityInfo.toString());
+		}
+		AuthHandler authHandler = null;
+		if (authCount > 0) {
+			authHandler = SecurityTypeState.selectAuthHandler(cap, authCaps);
+			for (int i = 0; i < authCount; ++i) {
+				if (authCaps.isSupported(cap[i])) {
+					//sending back RFB capability code
+					writer.writeInt32(cap[i]);
+					break;
+				}
+			}
+    	} else {
+    		authHandler = SecurityType.getAuthHandlerById(SecurityType.NONE_AUTHENTICATION.getId());
+    	}
+		Logger.getLogger("com.glavsoft.rfb.protocol.auth").info("Auth capability accepted: " + authHandler.getName());
+		authHandler.authenticate(reader, writer, authCaps, passwordRetriever);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/auth/VncAuthentication.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,100 @@
+// 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.rfb.protocol.auth;
+
+import com.glavsoft.exceptions.CryptoException;
+import com.glavsoft.exceptions.FatalException;
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.CapabilityContainer;
+import com.glavsoft.rfb.IPasswordRetriever;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.transport.Writer;
+
+import javax.crypto.*;
+import javax.crypto.spec.DESKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+public class VncAuthentication extends AuthHandler {
+	@Override
+	public SecurityType getType() {
+		return SecurityType.VNC_AUTHENTICATION;
+	}
+
+	@Override
+	public boolean authenticate(Reader reader,
+			Writer writer, CapabilityContainer authCaps, IPasswordRetriever passwordRetriever)
+	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));
+	    return false;
+	}
+
+  /**
+	 * Encript challenge by key using DES
+	 * @return encripted bytes
+	 * @throws CryptoException on problem with DES algorithm support or smth about
+	 */
+	public byte[] encrypt(byte[] challenge, byte[] key) throws CryptoException {
+		try {
+		    DESKeySpec desKeySpec = new DESKeySpec(mirrorBits(key));
+		    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
+		    SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
+		    Cipher desCipher = Cipher.getInstance("DES/ECB/NoPadding");
+		    desCipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            return desCipher.doFinal(challenge);
+		} catch (NoSuchAlgorithmException e) {
+			throw new CryptoException("Cannot encrypt challenge", e);
+		} catch (NoSuchPaddingException e) {
+			throw new CryptoException("Cannot encrypt challenge", e);
+		} catch (IllegalBlockSizeException e) {
+			throw new CryptoException("Cannot encrypt challenge", e);
+		} catch (BadPaddingException e) {
+			throw new CryptoException("Cannot encrypt challenge", e);
+		} catch (InvalidKeyException e) {
+			throw new CryptoException("Cannot encrypt challenge", e);
+		} catch (InvalidKeySpecException e) {
+			throw new CryptoException("Cannot encrypt challenge", e);
+		}
+	}
+
+	private byte[] mirrorBits(byte[] k) {
+		byte[] key = new byte[8];
+		for (int i = 0; i < 8; i++) {
+			byte s = k[i];
+			s = (byte) (((s >> 1) & 0x55) | ((s << 1) & 0xaa));
+	        s = (byte) (((s >> 2) & 0x33) | ((s << 2) & 0xcc));
+	        s = (byte) (((s >> 4) & 0x0f) | ((s << 4) & 0xf0));
+	        key[i] = s;
+		}
+		return key;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/AuthenticationState.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,84 @@
+// 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.rfb.protocol.state;
+
+import com.glavsoft.exceptions.*;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.auth.AuthHandler;
+
+public class AuthenticationState extends ProtocolState {
+
+	private static final int AUTH_RESULT_OK = 0;
+//	private static final int AUTH_RESULT_FAILED = 1;
+//	private static final int AUTH_RESULT_TOO_MANY = 2;
+	private final AuthHandler authHandler;
+
+	public AuthenticationState(ProtocolContext context,
+			AuthHandler authHandler) {
+		super(context);
+		this.authHandler = authHandler;
+	}
+
+	@Override
+	public boolean next() throws UnsupportedProtocolVersionException, TransportException,
+			UnsupportedSecurityTypeException, AuthenticationFailedException, FatalException {
+		authenticate();
+		return true;
+	}
+
+	private void authenticate() throws TransportException, AuthenticationFailedException,
+			FatalException, UnsupportedSecurityTypeException {
+		boolean isTight = authHandler.authenticate(reader, writer,
+				context.getSettings().authCapabilities, context.getPasswordRetriever());
+		// skip when protocol < 3.8 and NONE_AUTH
+		if (authHandler.useSecurityResult()) {
+			checkSecurityResult();
+		}
+		changeStateTo(isTight ? new InitTightState(context) : new InitState(context));
+		context.setTight(isTight);
+	}
+
+	/**
+	 * Check Security Result received from server
+	 * May be:
+	 * * 0 - OK
+	 * * 1 - Failed
+	 * @throws TransportException
+	 * @throws AuthenticationFailedException
+	 */
+	protected void checkSecurityResult() throws TransportException,
+	AuthenticationFailedException {
+		if (reader.readInt32() != AUTH_RESULT_OK) {
+			try {
+				String reason = reader.readString();
+				throw new AuthenticationFailedException(reason);
+			} catch (ClosedConnectionException e) {
+				// protocol version 3.3 and 3.7 does not send reason string,
+				// but silently closes the connection
+				throw new AuthenticationFailedException("Authentication failed");
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/HandshakeState.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,97 @@
+// 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.rfb.protocol.state;
+
+import com.glavsoft.exceptions.TransportException;
+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;
+
+public class HandshakeState extends ProtocolState {
+
+	public static final String PROTOCOL_VERSION_3_8 = "3.8";
+	public static final String PROTOCOL_VERSION_3_7 = "3.7";
+	public static final String PROTOCOL_VERSION_3_3 = "3.3";
+	private static final int PROTOCOL_STRING_LENGTH = 12;
+	private static final String PROTOCOL_STRING_REGEXP = "^RFB (\\d\\d\\d).(\\d\\d\\d)\n$";
+
+	private static final int MIN_SUPPORTED_VERSION_MAJOR = 3;
+	private static final int MIN_SUPPORTED_VERSION_MINOR = 3;
+
+	private static final int MAX_SUPPORTED_VERSION_MAJOR = 3;
+	private static final int MAX_SUPPORTED_VERSION_MINOR = 8;
+
+	public HandshakeState(ProtocolContext context) {
+		super(context);
+	}
+
+	@Override
+	public boolean next() throws UnsupportedProtocolVersionException, TransportException {
+		handshake();
+		return true;
+	}
+
+	private void handshake() throws TransportException, UnsupportedProtocolVersionException {
+		String protocolString = reader.readString(PROTOCOL_STRING_LENGTH);
+        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())
+			throw new UnsupportedProtocolVersionException(
+					"Unsupported protocol version: " + protocolString);
+		int major = Integer.parseInt(matcher.group(1));
+		int minor = Integer.parseInt(matcher.group(2));
+		if (major < MIN_SUPPORTED_VERSION_MAJOR ||
+				MIN_SUPPORTED_VERSION_MAJOR == major && minor <MIN_SUPPORTED_VERSION_MINOR)
+			throw new UnsupportedProtocolVersionException(
+					"Unsupported protocol version: " + major + "." + minor);
+		if (major > MAX_SUPPORTED_VERSION_MAJOR) {
+			major = MAX_SUPPORTED_VERSION_MAJOR;
+			minor = MAX_SUPPORTED_VERSION_MINOR;
+		}
+
+		if (minor >= MIN_SUPPORTED_VERSION_MINOR && minor < 7) {
+			changeStateTo(new SecurityType33State(context));
+			context.setProtocolVersion(PROTOCOL_VERSION_3_3);
+			minor = 3;
+		} else if (7 == minor) {
+			changeStateTo(new SecurityType37State(context));
+			context.setProtocolVersion(PROTOCOL_VERSION_3_7);
+			minor = 7;
+		} else if (minor >= MAX_SUPPORTED_VERSION_MINOR) {
+			changeStateTo(new SecurityTypeState(context));
+			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.getLogger(getClass().getName()).info("Set protocol version to: " + context.getProtocolVersion());
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/InitState.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,93 @@
+// 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.rfb.protocol.state;
+
+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.rfb.encoding.ServerInitMessage;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+
+import java.util.logging.Logger;
+
+/**
+ * ClientInit
+ *
+ * Once the client and server are sure that they're happy to talk to one
+ * another, the client sends an initialisation message.  At present this
+ * message onl@!,@!,y consists of a boolean indicating whether the server should try
+ * to share the desktop by leaving other clients connected, or give exclusive
+ * access to this client by disconnecting all other clients.
+ *
+ * 1 - U8 - shared-flag
+ *
+ * Shared-flag is non-zero (true) if the server should try to share the desktop by leaving
+ * other clients connected, zero (false) if it should give exclusive access to this client by
+ * disconnecting all other clients.
+ *
+ * ServerInit
+ *
+ * After receiving the ClientInit message, the server sends a ServerInit message. This
+ * tells the client the width and height of the server’s framebuffer, its pixel format and the
+ * name associated with the desktop.
+ */
+public class InitState extends ProtocolState {
+
+	public InitState(ProtocolContext context) {
+		super(context);
+	}
+
+	@Override
+	public boolean next() throws UnsupportedProtocolVersionException, TransportException,
+			UnsupportedSecurityTypeException, AuthenticationFailedException, FatalException {
+		clientAndServerInit();
+		return false;
+	}
+
+	protected void clientAndServerInit() throws TransportException {
+		ServerInitMessage serverInitMessage = getServerInitMessage();
+		ProtocolSettings settings = context.getSettings();
+		settings.enableAllEncodingCaps();
+		completeContextData(serverInitMessage);
+	}
+
+	protected void completeContextData(ServerInitMessage serverInitMessage) {
+		context.setPixelFormat(serverInitMessage.getPixelFormat());
+		context.setFbWidth(serverInitMessage.getFrameBufferWidth());
+		context.setFbHeight(serverInitMessage.getFrameBufferHeight());
+		context.setRemoteDesktopName(serverInitMessage.getName());
+        Logger.getLogger(getClass().getName()).fine(serverInitMessage.toString());
+	}
+
+	protected ServerInitMessage getServerInitMessage() throws TransportException {
+		writer.write(context.getSettings().getSharedFlag());
+        return new ServerInitMessage(reader);
+	}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/InitTightState.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,75 @@
+// 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.rfb.protocol.state;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.rfb.encoding.ServerInitMessage;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+
+/**
+ * Server Interaction Capabilities Message (protocol versions 3.7t, 3.8t)
+ *
+ * If TightVNC protocol extensions are enabled, the server informs the client
+ * what message types it supports in addition to ones defined in the standard
+ * RFB protocol.
+ * Also, the server sends the list of all supported encodings (note that it's
+ * not necessary to advertise the "raw" encoding sinse it MUST be supported in
+ * RFB 3.x protocols).
+ *
+ * This data immediately follows the server initialisation message.
+ */
+public class InitTightState extends InitState {
+
+	public InitTightState(ProtocolContext context) {
+		super(context);
+	}
+
+	/**
+	 * typedef struct _rfbInteractionCapsMsg {
+	 * 		CARD16 nServerMessageTypes;
+	 * 		CARD16 nClientMessageTypes;
+	 * 		CARD16 nEncodingTypes;
+	 * 		CARD16 pad;><------><------>// reserved, must be 0
+	 * 		// followed by nServerMessageTypes * rfbCapabilityInfo structures
+	 * 		// followed by nClientMessageTypes * rfbCapabilityInfo structures
+	 * } rfbInteractionCapsMsg;
+	 * #define sz_rfbInteractionCapsMsg 8
+	 */
+	@Override
+	protected void clientAndServerInit() throws TransportException {
+		ServerInitMessage serverInitMessage = getServerInitMessage();
+		int nServerMessageTypes = reader.readUInt16();
+		int nClientMessageTypes = reader.readUInt16();
+		int nEncodingTypes = reader.readUInt16();
+		reader.readUInt16(); //padding
+		ProtocolSettings settings = context.getSettings();
+		settings.serverMessagesCapabilities.read(reader, nServerMessageTypes);
+		settings.clientMessagesCapabilities.read(reader, nClientMessageTypes);
+		settings.encodingTypesCapabilities.read(reader, nEncodingTypes);
+		completeContextData(serverInitMessage);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/ProtocolState.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,60 @@
+// 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.rfb.protocol.state;
+
+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 Reader reader;
+	protected Writer writer;
+
+	public ProtocolState(ProtocolContext context) {
+		this.context = context;
+		this.reader = context.getReader();
+		this.writer = context.getWriter();
+	}
+
+	/**
+	 * Change state of finite machine.
+	 *
+	 * @param state state, the Finite Machine will switched to
+	 */
+	protected void changeStateTo(ProtocolState state) {
+		context.changeStateTo(state);
+	}
+
+	/**
+	 * Carry out next step of protocol flow.
+	 *
+	 * @return false when no next protocol steps availabe, true - when need to continue
+	 */
+	abstract public boolean next() throws UnsupportedProtocolVersionException, TransportException,
+			UnsupportedSecurityTypeException, AuthenticationFailedException, FatalException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/SecurityType33State.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,61 @@
+// 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.rfb.protocol.state;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+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) {
+		super(context);
+	}
+
+	@Override
+	protected void negotiateAboutSecurityType()
+			throws TransportException, UnsupportedSecurityTypeException {
+        Logger.getLogger(getClass().getName()).info("Get Security Type");
+		int type = reader.readInt32();
+		Logger.getLogger(getClass().getName()).info("Type received: " + type);
+		if (0 == type)
+			// throw exception with reason
+			throw new UnsupportedSecurityTypeException(reader.readString());
+		AuthHandler typeSelected = selectAuthHandler(new byte[] {(byte) (0xff & type)},
+					context.getSettings().authCapabilities);
+		if (typeSelected != null) {
+			setUseSecurityResult(typeSelected);
+			Logger.getLogger(getClass().getName()).info("Type accepted: " + typeSelected.getName());
+		} else
+			throw new UnsupportedSecurityTypeException(
+					"No security types supported. Server sent '" +
+					type + "' security type, but we do not support it.");
+		changeStateTo(new AuthenticationState(context, typeSelected));
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/SecurityType37State.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,43 @@
+// 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.rfb.protocol.state;
+
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.auth.AuthHandler;
+import com.glavsoft.rfb.protocol.auth.SecurityType;
+
+public class SecurityType37State extends SecurityTypeState {
+
+	public SecurityType37State(ProtocolContext context) {
+		super(context);
+	}
+
+	@Override
+	protected void setUseSecurityResult(AuthHandler type) {
+		if (SecurityType.NONE_AUTHENTICATION == type.getType()) {
+			type.setUseSecurityResult(false);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/rfb/protocol/state/SecurityTypeState.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,107 @@
+// 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.rfb.protocol.state;
+
+import com.glavsoft.exceptions.TransportException;
+import com.glavsoft.exceptions.UnsupportedProtocolVersionException;
+import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
+import com.glavsoft.rfb.CapabilityContainer;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.auth.AuthHandler;
+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) {
+		super(context);
+	}
+
+	@Override
+	public boolean next() throws UnsupportedProtocolVersionException, TransportException, UnsupportedSecurityTypeException {
+		negotiateAboutSecurityType();
+		return true;
+	}
+
+	protected void negotiateAboutSecurityType() throws TransportException,
+			UnsupportedSecurityTypeException {
+		int secTypesNum = reader.readUInt8();
+		if (0 == secTypesNum)
+			// throw exception with reason
+			throw new UnsupportedSecurityTypeException(reader.readString());
+		byte[] secTypes = reader.readBytes(secTypesNum);
+        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.getLogger(getClass().getName()).info("Security Type accepted: " + typeSelected.getName());
+
+		changeStateTo(new AuthenticationState(context, typeSelected));
+	}
+
+	/**
+	 * Select apropriate security type we supporded from types which server sent
+	 *
+	 * @param secTypes - byte array with security types server supported
+	 * @param authCapabilities
+	 * @return {@link AuthHandler} of selected type
+	 * @throws UnsupportedSecurityTypeException when no security types server sent we support
+	 */
+	public static AuthHandler selectAuthHandler(byte[] secTypes, CapabilityContainer authCapabilities)
+	throws UnsupportedSecurityTypeException {
+		AuthHandler typeSelected = null;
+		// Tigh Authentication first
+		for (byte type : secTypes) {
+			if (SecurityType.TIGHT_AUTHENTICATION.getId() == (0xff & type)) {
+				typeSelected = SecurityType.implementedSecurityTypes
+					.get(SecurityType.TIGHT_AUTHENTICATION.getId());
+				if (typeSelected != null)
+					return typeSelected;
+			}
+		}
+		for (byte type : secTypes) {
+			typeSelected = SecurityType.implementedSecurityTypes.get(0xff & type);
+			if (typeSelected != null &&
+					authCapabilities.isSupported(typeSelected.getId()))
+				return typeSelected;
+		}
+		throw new UnsupportedSecurityTypeException(
+				"No security types supported. Server sent '"
+						+ Strings.toString(secTypes)
+						+ "' security types, but we do not support any of their.");
+	}
+
+	/**
+	 * @param typeSelected
+	 */
+	protected void setUseSecurityResult(AuthHandler typeSelected) {
+		// nop for Protocol version 3.8 and above
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/transport/Reader.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,166 @@
+// 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.transport;
+
+import com.glavsoft.exceptions.ClosedConnectionException;
+import com.glavsoft.exceptions.TransportException;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+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) {
+		this.is = new DataInputStream(new BufferedInputStream(is));
+	}
+
+	public byte readByte() throws TransportException {
+		try {
+			byte readByte = is.readByte();
+			return readByte;
+		} catch (EOFException e) {
+			throw new ClosedConnectionException(e);
+		} catch (IOException e) {
+			throw new TransportException("Cannot read byte", e);
+		}
+
+	}
+
+	public int readUInt8() throws TransportException {
+		return readByte() & 0x0ff;
+	}
+
+	public int readUInt16() throws TransportException {
+		return readInt16() & 0x0ffff;
+	}
+
+	public short readInt16() throws TransportException {
+		try {
+			short readShort = is.readShort();
+			return readShort;
+		} catch (EOFException e) {
+			throw new ClosedConnectionException(e);
+		} catch (IOException e) {
+			throw new TransportException("Cannot read int16", e);
+		}
+	}
+
+	public long readUInt32() throws TransportException {
+		return readInt32() & 0xffffffffL;
+	}
+
+	public int readInt32() throws TransportException {
+		try {
+			int readInt = is.readInt();
+			return readInt;
+		} catch (EOFException e) {
+			throw new ClosedConnectionException(e);
+		} catch (IOException 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);
+		}
+	}
+
+	/**
+	 * Read string by it length.
+	 * Use this method only when sure no character accept ASCII will be read.
+	 * Use readBytes and character encoding conversion instead.
+	 *
+	 * @return String read
+	 */
+	public String readString(int length) throws TransportException {
+        return new String(readBytes(length));
+	}
+
+	/**
+	 * 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 or {@link #readUtf8String} method
+	 * when utf-8 encoding needed.
+	 *
+	 * @return String read
+	 * @throws TransportException
+	 */
+	public String readString() 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 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);
+	}
+
+	public byte[] readBytes(byte[] b, int offset, int length) throws TransportException {
+		try {
+			is.readFully(b, offset, length);
+			return b;
+		} catch (EOFException e) {
+			throw new ClosedConnectionException(e);
+		} catch (IOException e) {
+			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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/transport/Writer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,108 @@
+// 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.transport;
+
+import com.glavsoft.exceptions.TransportException;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class Writer {
+	private final DataOutputStream os;
+
+	public Writer(OutputStream os) {
+		this.os = new DataOutputStream(os);
+	}
+
+	public void flush() throws TransportException {
+		try {
+			os.flush();
+		} catch (IOException e) {
+			throw new TransportException("Cannot flush output stream", e);
+		}
+	}
+
+	public void writeByte(int b) throws TransportException {
+		write((byte) (b & 0xff));
+	}
+
+	public void write(byte b) throws TransportException {
+		try {
+			os.writeByte(b);
+		} catch (IOException e) {
+			throw new TransportException("Cannot write byte", e);
+		}
+	}
+
+	public void writeInt16(int sh) throws TransportException {
+		write((short) (sh & 0xffff));
+	}
+
+	public void write(short sh) throws TransportException {
+		try {
+			os.writeShort(sh);
+		} catch (IOException e) {
+			throw new TransportException("Cannot write short", e);
+		}
+	}
+
+	public void writeInt32(int i) throws TransportException {
+		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);
+		} catch (IOException e) {
+			throw new TransportException("Cannot write int", e);
+		}
+	}
+
+	public void write(byte[] b) throws TransportException {
+		write(b, 0, b.length);
+	}
+
+	public void write(byte[] b, int length) throws TransportException {
+		write(b, 0, length);
+	}
+
+	public void write(byte[] b, int offset, int length)
+			throws TransportException {
+		try {
+			os.write(b, offset, length <= b.length ? length : b.length);
+		} catch (IOException e) {
+			throw new TransportException("Cannot write " + length + " bytes", e);
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/utils/Keymap.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,905 @@
+// 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.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author dime at tightvnc.com
+ */
+public class Keymap {
+
+	public static final int K_F1 = 0xffbe;
+	public static final int K_F2 = 0xffbf;
+	public static final int K_F3 = 0xffc0;
+	public static final int K_F4 = 0xffc1;
+	public static final int K_F5 = 0xffc2;
+	public static final int K_F6 = 0xffc3;
+	public static final int K_F7 = 0xffc4;
+	public static final int K_F8 = 0xffc5;
+	public static final int K_F9 = 0xffc6;
+	public static final int K_F10 = 0xffc7;
+	public static final int K_F11 = 0xffc8;
+	public static final int K_F12 = 0xffc9;
+	public static final int K_INSERT = 0xff63;
+	public static final int K_DELETE = 0xffff;
+	public static final int K_HOME = 0xff50;
+	public static final int K_END = 0xff57;
+	public static final int K_PAGE_DOWN = 0xff56;
+	public static final int K_PAGE_UP = 0xff55;
+	public static final int K_DOWN = 0xff54;
+	public static final int K_RIGHT = 0xff53;
+	public static final int K_UP = 0xff52;
+	public static final int K_LEFT = 0xff51;
+	public static final int K_ESCAPE = 0xff1b;
+	public static final int K_ENTER = 0xff0d;
+	public static final int K_TAB = 0xff09;
+	public static final int K_BACK_SPACE = 0xff08;
+	public static final int K_ALT_LEFT = 0xffe9;
+	public static final int K_META_LEFT = 0xffe7;
+	public static final int K_SHIFT_LEFT = 0xffe1;
+	public static final int K_CTRL_LEFT = 0xffe3;
+	public static final int K_SUPER_LEFT = 0xffeb;
+	public static final int K_HYPER_LEFT = 0xffed;
+
+	public static final int K_KP_SPACE = 0xFF80;
+	public static final int K_KP_TAB = 0xFF89;
+	public static final int K_KP_ENTER = 0xFF8D;
+	public static final int K_KP_F1 = 0xFF91;
+	public static final int K_KP_F2 = 0xFF92;
+	public static final int K_KP_F3 = 0xFF93;
+	public static final int K_KP_F4 = 0xFF94;
+	public static final int K_KP_HOME = 0xFF95;
+	public static final int K_KP_LEFT = 0xFF96;
+	public static final int K_KP_UP = 0xFF97;
+	public static final int K_KP_RIGHT = 0xFF98;
+	public static final int K_KP_DOWN = 0xFF99;
+	public static final int K_KP_PRIOR = 0xFF9A;
+	public static final int K_KP_PAGE_UP = 0xFF9A;
+	public static final int K_KP_NEXT = 0xFF9B;
+	public static final int K_KP_PAGE_DOWN = 0xFF9B;
+	public static final int K_KP_END = 0xFF9C;
+	public static final int K_KP_BEGIN = 0xFF9D;
+	public static final int K_KP_INSERT = 0xFF9E;
+	public static final int K_KP_DELETE = 0xFF9F;
+	public static final int K_KP_EQUAL = 0xFFBD;
+
+	public static final int K_KP_MULTIPLY = 0xFFAA;
+	public static final int K_KP_ADD = 0xFFAB;
+	public static final int K_KP_SEPARATOR = 0xFFAC; /* separator, often comma */
+	public static final int K_KP_SUBTRACT = 0xFFAD;
+	public static final int K_KP_DECIMAL = 0xFFAE;
+	public static final int K_KP_DIVIDE = 0xFFAF;
+
+	public static final int K_KP_0 = 0xFFB0;
+	public static final int K_KP_1 = 0xFFB1;
+	public static final int K_KP_2 = 0xFFB2;
+	public static final int K_KP_3 = 0xFFB3;
+	public static final int K_KP_4 = 0xFFB4;
+	public static final int K_KP_5 = 0xFFB5;
+	public static final int K_KP_6 = 0xFFB6;
+	public static final int K_KP_7 = 0xFFB7;
+	public static final int K_KP_8 = 0xFFB8;
+	public static final int K_KP_9 = 0xFFB9;
+
+	private static Map<Integer, Integer> keyMap = toMap(new int[][] {
+		// X      Unicode
+		{ 0x01a1, 0x0104 }, /*                     Aogonek  LATIN CAPITAL LETTER A WITH OGONEK */
+		{ 0x01a2, 0x02d8 }, /*                       breve  BREVE */
+		{ 0x01a3, 0x0141 }, /*                     Lstroke  LATIN CAPITAL LETTER L WITH STROKE */
+		{ 0x01a5, 0x013d }, /*                      Lcaron  LATIN CAPITAL LETTER L WITH CARON */
+		{ 0x01a6, 0x015a }, /*                      Sacute  LATIN CAPITAL LETTER S WITH ACUTE */
+		{ 0x01a9, 0x0160 }, /*                      Scaron  LATIN CAPITAL LETTER S WITH CARON */
+		{ 0x01aa, 0x015e }, /*                    Scedilla  LATIN CAPITAL LETTER S WITH CEDILLA */
+		{ 0x01ab, 0x0164 }, /*                      Tcaron  LATIN CAPITAL LETTER T WITH CARON */
+		{ 0x01ac, 0x0179 }, /*                      Zacute  LATIN CAPITAL LETTER Z WITH ACUTE */
+		{ 0x01ae, 0x017d }, /*                      Zcaron  LATIN CAPITAL LETTER Z WITH CARON */
+		{ 0x01af, 0x017b }, /*                   Zabovedot  LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+		{ 0x01b1, 0x0105 }, /*                     aogonek  LATIN SMALL LETTER A WITH OGONEK */
+		{ 0x01b2, 0x02db }, /*                      ogonek  OGONEK */
+		{ 0x01b3, 0x0142 }, /*                     lstroke  LATIN SMALL LETTER L WITH STROKE */
+		{ 0x01b5, 0x013e }, /*                      lcaron  LATIN SMALL LETTER L WITH CARON */
+		{ 0x01b6, 0x015b }, /*                      sacute  LATIN SMALL LETTER S WITH ACUTE */
+		{ 0x01b7, 0x02c7 }, /*                       caron  CARON */
+		{ 0x01b9, 0x0161 }, /*                      scaron  LATIN SMALL LETTER S WITH CARON */
+		{ 0x01ba, 0x015f }, /*                    scedilla  LATIN SMALL LETTER S WITH CEDILLA */
+		{ 0x01bb, 0x0165 }, /*                      tcaron  LATIN SMALL LETTER T WITH CARON */
+		{ 0x01bc, 0x017a }, /*                      zacute  LATIN SMALL LETTER Z WITH ACUTE */
+		{ 0x01bd, 0x02dd }, /*                 doubleacute  DOUBLE ACUTE ACCENT */
+		{ 0x01be, 0x017e }, /*                      zcaron  LATIN SMALL LETTER Z WITH CARON */
+		{ 0x01bf, 0x017c }, /*                   zabovedot  LATIN SMALL LETTER Z WITH DOT ABOVE */
+		{ 0x01c0, 0x0154 }, /*                      Racute  LATIN CAPITAL LETTER R WITH ACUTE */
+		{ 0x01c3, 0x0102 }, /*                      Abreve  LATIN CAPITAL LETTER A WITH BREVE */
+		{ 0x01c5, 0x0139 }, /*                      Lacute  LATIN CAPITAL LETTER L WITH ACUTE */
+		{ 0x01c6, 0x0106 }, /*                      Cacute  LATIN CAPITAL LETTER C WITH ACUTE */
+		{ 0x01c8, 0x010c }, /*                      Ccaron  LATIN CAPITAL LETTER C WITH CARON */
+		{ 0x01ca, 0x0118 }, /*                     Eogonek  LATIN CAPITAL LETTER E WITH OGONEK */
+		{ 0x01cc, 0x011a }, /*                      Ecaron  LATIN CAPITAL LETTER E WITH CARON */
+		{ 0x01cf, 0x010e }, /*                      Dcaron  LATIN CAPITAL LETTER D WITH CARON */
+		{ 0x01d0, 0x0110 }, /*                     Dstroke  LATIN CAPITAL LETTER D WITH STROKE */
+		{ 0x01d1, 0x0143 }, /*                      Nacute  LATIN CAPITAL LETTER N WITH ACUTE */
+		{ 0x01d2, 0x0147 }, /*                      Ncaron  LATIN CAPITAL LETTER N WITH CARON */
+		{ 0x01d5, 0x0150 }, /*                Odoubleacute  LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+		{ 0x01d8, 0x0158 }, /*                      Rcaron  LATIN CAPITAL LETTER R WITH CARON */
+		{ 0x01d9, 0x016e }, /*                       Uring  LATIN CAPITAL LETTER U WITH RING ABOVE */
+		{ 0x01db, 0x0170 }, /*                Udoubleacute  LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+		{ 0x01de, 0x0162 }, /*                    Tcedilla  LATIN CAPITAL LETTER T WITH CEDILLA */
+		{ 0x01e0, 0x0155 }, /*                      racute  LATIN SMALL LETTER R WITH ACUTE */
+		{ 0x01e3, 0x0103 }, /*                      abreve  LATIN SMALL LETTER A WITH BREVE */
+		{ 0x01e5, 0x013a }, /*                      lacute  LATIN SMALL LETTER L WITH ACUTE */
+		{ 0x01e6, 0x0107 }, /*                      cacute  LATIN SMALL LETTER C WITH ACUTE */
+		{ 0x01e8, 0x010d }, /*                      ccaron  LATIN SMALL LETTER C WITH CARON */
+		{ 0x01ea, 0x0119 }, /*                     eogonek  LATIN SMALL LETTER E WITH OGONEK */
+		{ 0x01ec, 0x011b }, /*                      ecaron  LATIN SMALL LETTER E WITH CARON */
+		{ 0x01ef, 0x010f }, /*                      dcaron  LATIN SMALL LETTER D WITH CARON */
+		{ 0x01f0, 0x0111 }, /*                     dstroke  LATIN SMALL LETTER D WITH STROKE */
+		{ 0x01f1, 0x0144 }, /*                      nacute  LATIN SMALL LETTER N WITH ACUTE */
+		{ 0x01f2, 0x0148 }, /*                      ncaron  LATIN SMALL LETTER N WITH CARON */
+		{ 0x01f5, 0x0151 }, /*                odoubleacute  LATIN SMALL LETTER O WITH DOUBLE ACUTE */
+		{ 0x01f8, 0x0159 }, /*                      rcaron  LATIN SMALL LETTER R WITH CARON */
+		{ 0x01f9, 0x016f }, /*                       uring  LATIN SMALL LETTER U WITH RING ABOVE */
+		{ 0x01fb, 0x0171 }, /*                udoubleacute  LATIN SMALL LETTER U WITH DOUBLE ACUTE */
+		{ 0x01fe, 0x0163 }, /*                    tcedilla  LATIN SMALL LETTER T WITH CEDILLA */
+		{ 0x01ff, 0x02d9 }, /*                    abovedot  DOT ABOVE */
+		{ 0x02a1, 0x0126 }, /*                     Hstroke  LATIN CAPITAL LETTER H WITH STROKE */
+		{ 0x02a6, 0x0124 }, /*                 Hcircumflex  LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+		{ 0x02a9, 0x0130 }, /*                   Iabovedot  LATIN CAPITAL LETTER I WITH DOT ABOVE */
+		{ 0x02ab, 0x011e }, /*                      Gbreve  LATIN CAPITAL LETTER G WITH BREVE */
+		{ 0x02ac, 0x0134 }, /*                 Jcircumflex  LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+		{ 0x02b1, 0x0127 }, /*                     hstroke  LATIN SMALL LETTER H WITH STROKE */
+		{ 0x02b6, 0x0125 }, /*                 hcircumflex  LATIN SMALL LETTER H WITH CIRCUMFLEX */
+		{ 0x02b9, 0x0131 }, /*                    idotless  LATIN SMALL LETTER DOTLESS I */
+		{ 0x02bb, 0x011f }, /*                      gbreve  LATIN SMALL LETTER G WITH BREVE */
+		{ 0x02bc, 0x0135 }, /*                 jcircumflex  LATIN SMALL LETTER J WITH CIRCUMFLEX */
+		{ 0x02c5, 0x010a }, /*                   Cabovedot  LATIN CAPITAL LETTER C WITH DOT ABOVE */
+		{ 0x02c6, 0x0108 }, /*                 Ccircumflex  LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+		{ 0x02d5, 0x0120 }, /*                   Gabovedot  LATIN CAPITAL LETTER G WITH DOT ABOVE */
+		{ 0x02d8, 0x011c }, /*                 Gcircumflex  LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+		{ 0x02dd, 0x016c }, /*                      Ubreve  LATIN CAPITAL LETTER U WITH BREVE */
+		{ 0x02de, 0x015c }, /*                 Scircumflex  LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+		{ 0x02e5, 0x010b }, /*                   cabovedot  LATIN SMALL LETTER C WITH DOT ABOVE */
+		{ 0x02e6, 0x0109 }, /*                 ccircumflex  LATIN SMALL LETTER C WITH CIRCUMFLEX */
+		{ 0x02f5, 0x0121 }, /*                   gabovedot  LATIN SMALL LETTER G WITH DOT ABOVE */
+		{ 0x02f8, 0x011d }, /*                 gcircumflex  LATIN SMALL LETTER G WITH CIRCUMFLEX */
+		{ 0x02fd, 0x016d }, /*                      ubreve  LATIN SMALL LETTER U WITH BREVE */
+		{ 0x02fe, 0x015d }, /*                 scircumflex  LATIN SMALL LETTER S WITH CIRCUMFLEX */
+		{ 0x03a2, 0x0138 }, /*                         kra  LATIN SMALL LETTER KRA */
+		{ 0x03a3, 0x0156 }, /*                    Rcedilla  LATIN CAPITAL LETTER R WITH CEDILLA */
+		{ 0x03a5, 0x0128 }, /*                      Itilde  LATIN CAPITAL LETTER I WITH TILDE */
+		{ 0x03a6, 0x013b }, /*                    Lcedilla  LATIN CAPITAL LETTER L WITH CEDILLA */
+		{ 0x03aa, 0x0112 }, /*                     Emacron  LATIN CAPITAL LETTER E WITH MACRON */
+		{ 0x03ab, 0x0122 }, /*                    Gcedilla  LATIN CAPITAL LETTER G WITH CEDILLA */
+		{ 0x03ac, 0x0166 }, /*                      Tslash  LATIN CAPITAL LETTER T WITH STROKE */
+		{ 0x03b3, 0x0157 }, /*                    rcedilla  LATIN SMALL LETTER R WITH CEDILLA */
+		{ 0x03b5, 0x0129 }, /*                      itilde  LATIN SMALL LETTER I WITH TILDE */
+		{ 0x03b6, 0x013c }, /*                    lcedilla  LATIN SMALL LETTER L WITH CEDILLA */
+		{ 0x03ba, 0x0113 }, /*                     emacron  LATIN SMALL LETTER E WITH MACRON */
+		{ 0x03bb, 0x0123 }, /*                    gcedilla  LATIN SMALL LETTER G WITH CEDILLA */
+		{ 0x03bc, 0x0167 }, /*                      tslash  LATIN SMALL LETTER T WITH STROKE */
+		{ 0x03bd, 0x014a }, /*                         ENG  LATIN CAPITAL LETTER ENG */
+		{ 0x03bf, 0x014b }, /*                         eng  LATIN SMALL LETTER ENG */
+		{ 0x03c0, 0x0100 }, /*                     Amacron  LATIN CAPITAL LETTER A WITH MACRON */
+		{ 0x03c7, 0x012e }, /*                     Iogonek  LATIN CAPITAL LETTER I WITH OGONEK */
+		{ 0x03cc, 0x0116 }, /*                   Eabovedot  LATIN CAPITAL LETTER E WITH DOT ABOVE */
+		{ 0x03cf, 0x012a }, /*                     Imacron  LATIN CAPITAL LETTER I WITH MACRON */
+		{ 0x03d1, 0x0145 }, /*                    Ncedilla  LATIN CAPITAL LETTER N WITH CEDILLA */
+		{ 0x03d2, 0x014c }, /*                     Omacron  LATIN CAPITAL LETTER O WITH MACRON */
+		{ 0x03d3, 0x0136 }, /*                    Kcedilla  LATIN CAPITAL LETTER K WITH CEDILLA */
+		{ 0x03d9, 0x0172 }, /*                     Uogonek  LATIN CAPITAL LETTER U WITH OGONEK */
+		{ 0x03dd, 0x0168 }, /*                      Utilde  LATIN CAPITAL LETTER U WITH TILDE */
+		{ 0x03de, 0x016a }, /*                     Umacron  LATIN CAPITAL LETTER U WITH MACRON */
+		{ 0x03e0, 0x0101 }, /*                     amacron  LATIN SMALL LETTER A WITH MACRON */
+		{ 0x03e7, 0x012f }, /*                     iogonek  LATIN SMALL LETTER I WITH OGONEK */
+		{ 0x03ec, 0x0117 }, /*                   eabovedot  LATIN SMALL LETTER E WITH DOT ABOVE */
+		{ 0x03ef, 0x012b }, /*                     imacron  LATIN SMALL LETTER I WITH MACRON */
+		{ 0x03f1, 0x0146 }, /*                    ncedilla  LATIN SMALL LETTER N WITH CEDILLA */
+		{ 0x03f2, 0x014d }, /*                     omacron  LATIN SMALL LETTER O WITH MACRON */
+		{ 0x03f3, 0x0137 }, /*                    kcedilla  LATIN SMALL LETTER K WITH CEDILLA */
+		{ 0x03f9, 0x0173 }, /*                     uogonek  LATIN SMALL LETTER U WITH OGONEK */
+		{ 0x03fd, 0x0169 }, /*                      utilde  LATIN SMALL LETTER U WITH TILDE */
+		{ 0x03fe, 0x016b }, /*                     umacron  LATIN SMALL LETTER U WITH MACRON */
+		{ 0x047e, 0x203e }, /*                    overline  OVERLINE */
+		{ 0x04a1, 0x3002 }, /*               kana_fullstop  IDEOGRAPHIC FULL STOP */
+		{ 0x04a2, 0x300c }, /*         kana_openingbracket  LEFT CORNER BRACKET */
+		{ 0x04a3, 0x300d }, /*         kana_closingbracket  RIGHT CORNER BRACKET */
+		{ 0x04a4, 0x3001 }, /*                  kana_comma  IDEOGRAPHIC COMMA */
+		{ 0x04a5, 0x30fb }, /*            kana_conjunctive  KATAKANA MIDDLE DOT */
+		{ 0x04a6, 0x30f2 }, /*                     kana_WO  KATAKANA LETTER WO */
+		{ 0x04a7, 0x30a1 }, /*                      kana_a  KATAKANA LETTER SMALL A */
+		{ 0x04a8, 0x30a3 }, /*                      kana_i  KATAKANA LETTER SMALL I */
+		{ 0x04a9, 0x30a5 }, /*                      kana_u  KATAKANA LETTER SMALL U */
+		{ 0x04aa, 0x30a7 }, /*                      kana_e  KATAKANA LETTER SMALL E */
+		{ 0x04ab, 0x30a9 }, /*                      kana_o  KATAKANA LETTER SMALL O */
+		{ 0x04ac, 0x30e3 }, /*                     kana_ya  KATAKANA LETTER SMALL YA */
+		{ 0x04ad, 0x30e5 }, /*                     kana_yu  KATAKANA LETTER SMALL YU */
+		{ 0x04ae, 0x30e7 }, /*                     kana_yo  KATAKANA LETTER SMALL YO */
+		{ 0x04af, 0x30c3 }, /*                    kana_tsu  KATAKANA LETTER SMALL TU */
+		{ 0x04b0, 0x30fc }, /*              prolongedsound  KATAKANA-HIRAGANA PROLONGED SOUND MARK */
+		{ 0x04b1, 0x30a2 }, /*                      kana_A  KATAKANA LETTER A */
+		{ 0x04b2, 0x30a4 }, /*                      kana_I  KATAKANA LETTER I */
+		{ 0x04b3, 0x30a6 }, /*                      kana_U  KATAKANA LETTER U */
+		{ 0x04b4, 0x30a8 }, /*                      kana_E  KATAKANA LETTER E */
+		{ 0x04b5, 0x30aa }, /*                      kana_O  KATAKANA LETTER O */
+		{ 0x04b6, 0x30ab }, /*                     kana_KA  KATAKANA LETTER KA */
+		{ 0x04b7, 0x30ad }, /*                     kana_KI  KATAKANA LETTER KI */
+		{ 0x04b8, 0x30af }, /*                     kana_KU  KATAKANA LETTER KU */
+		{ 0x04b9, 0x30b1 }, /*                     kana_KE  KATAKANA LETTER KE */
+		{ 0x04ba, 0x30b3 }, /*                     kana_KO  KATAKANA LETTER KO */
+		{ 0x04bb, 0x30b5 }, /*                     kana_SA  KATAKANA LETTER SA */
+		{ 0x04bc, 0x30b7 }, /*                    kana_SHI  KATAKANA LETTER SI */
+		{ 0x04bd, 0x30b9 }, /*                     kana_SU  KATAKANA LETTER SU */
+		{ 0x04be, 0x30bb }, /*                     kana_SE  KATAKANA LETTER SE */
+		{ 0x04bf, 0x30bd }, /*                     kana_SO  KATAKANA LETTER SO */
+		{ 0x04c0, 0x30bf }, /*                     kana_TA  KATAKANA LETTER TA */
+		{ 0x04c1, 0x30c1 }, /*                    kana_CHI  KATAKANA LETTER TI */
+		{ 0x04c2, 0x30c4 }, /*                    kana_TSU  KATAKANA LETTER TU */
+		{ 0x04c3, 0x30c6 }, /*                     kana_TE  KATAKANA LETTER TE */
+		{ 0x04c4, 0x30c8 }, /*                     kana_TO  KATAKANA LETTER TO */
+		{ 0x04c5, 0x30ca }, /*                     kana_NA  KATAKANA LETTER NA */
+		{ 0x04c6, 0x30cb }, /*                     kana_NI  KATAKANA LETTER NI */
+		{ 0x04c7, 0x30cc }, /*                     kana_NU  KATAKANA LETTER NU */
+		{ 0x04c8, 0x30cd }, /*                     kana_NE  KATAKANA LETTER NE */
+		{ 0x04c9, 0x30ce }, /*                     kana_NO  KATAKANA LETTER NO */
+		{ 0x04ca, 0x30cf }, /*                     kana_HA  KATAKANA LETTER HA */
+		{ 0x04cb, 0x30d2 }, /*                     kana_HI  KATAKANA LETTER HI */
+		{ 0x04cc, 0x30d5 }, /*                     kana_FU  KATAKANA LETTER HU */
+		{ 0x04cd, 0x30d8 }, /*                     kana_HE  KATAKANA LETTER HE */
+		{ 0x04ce, 0x30db }, /*                     kana_HO  KATAKANA LETTER HO */
+		{ 0x04cf, 0x30de }, /*                     kana_MA  KATAKANA LETTER MA */
+		{ 0x04d0, 0x30df }, /*                     kana_MI  KATAKANA LETTER MI */
+		{ 0x04d1, 0x30e0 }, /*                     kana_MU  KATAKANA LETTER MU */
+		{ 0x04d2, 0x30e1 }, /*                     kana_ME  KATAKANA LETTER ME */
+		{ 0x04d3, 0x30e2 }, /*                     kana_MO  KATAKANA LETTER MO */
+		{ 0x04d4, 0x30e4 }, /*                     kana_YA  KATAKANA LETTER YA */
+		{ 0x04d5, 0x30e6 }, /*                     kana_YU  KATAKANA LETTER YU */
+		{ 0x04d6, 0x30e8 }, /*                     kana_YO  KATAKANA LETTER YO */
+		{ 0x04d7, 0x30e9 }, /*                     kana_RA  KATAKANA LETTER RA */
+		{ 0x04d8, 0x30ea }, /*                     kana_RI  KATAKANA LETTER RI */
+		{ 0x04d9, 0x30eb }, /*                     kana_RU  KATAKANA LETTER RU */
+		{ 0x04da, 0x30ec }, /*                     kana_RE  KATAKANA LETTER RE */
+		{ 0x04db, 0x30ed }, /*                     kana_RO  KATAKANA LETTER RO */
+		{ 0x04dc, 0x30ef }, /*                     kana_WA  KATAKANA LETTER WA */
+		{ 0x04dd, 0x30f3 }, /*                      kana_N  KATAKANA LETTER N */
+		{ 0x04de, 0x309b }, /*                 voicedsound  KATAKANA-HIRAGANA VOICED SOUND MARK */
+		{ 0x04df, 0x309c }, /*             semivoicedsound  KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+		{ 0x05ac, 0x060c }, /*                Arabic_comma  ARABIC COMMA */
+		{ 0x05bb, 0x061b }, /*            Arabic_semicolon  ARABIC SEMICOLON */
+		{ 0x05bf, 0x061f }, /*        Arabic_question_mark  ARABIC QUESTION MARK */
+		{ 0x05c1, 0x0621 }, /*                Arabic_hamza  ARABIC LETTER HAMZA */
+		{ 0x05c2, 0x0622 }, /*          Arabic_maddaonalef  ARABIC LETTER ALEF WITH MADDA ABOVE */
+		{ 0x05c3, 0x0623 }, /*          Arabic_hamzaonalef  ARABIC LETTER ALEF WITH HAMZA ABOVE */
+		{ 0x05c4, 0x0624 }, /*           Arabic_hamzaonwaw  ARABIC LETTER WAW WITH HAMZA ABOVE */
+		{ 0x05c5, 0x0625 }, /*       Arabic_hamzaunderalef  ARABIC LETTER ALEF WITH HAMZA BELOW */
+		{ 0x05c6, 0x0626 }, /*           Arabic_hamzaonyeh  ARABIC LETTER YEH WITH HAMZA ABOVE */
+		{ 0x05c7, 0x0627 }, /*                 Arabic_alef  ARABIC LETTER ALEF */
+		{ 0x05c8, 0x0628 }, /*                  Arabic_beh  ARABIC LETTER BEH */
+		{ 0x05c9, 0x0629 }, /*           Arabic_tehmarbuta  ARABIC LETTER TEH MARBUTA */
+		{ 0x05ca, 0x062a }, /*                  Arabic_teh  ARABIC LETTER TEH */
+		{ 0x05cb, 0x062b }, /*                 Arabic_theh  ARABIC LETTER THEH */
+		{ 0x05cc, 0x062c }, /*                 Arabic_jeem  ARABIC LETTER JEEM */
+		{ 0x05cd, 0x062d }, /*                  Arabic_hah  ARABIC LETTER HAH */
+		{ 0x05ce, 0x062e }, /*                 Arabic_khah  ARABIC LETTER KHAH */
+		{ 0x05cf, 0x062f }, /*                  Arabic_dal  ARABIC LETTER DAL */
+		{ 0x05d0, 0x0630 }, /*                 Arabic_thal  ARABIC LETTER THAL */
+		{ 0x05d1, 0x0631 }, /*                   Arabic_ra  ARABIC LETTER REH */
+		{ 0x05d2, 0x0632 }, /*                 Arabic_zain  ARABIC LETTER ZAIN */
+		{ 0x05d3, 0x0633 }, /*                 Arabic_seen  ARABIC LETTER SEEN */
+		{ 0x05d4, 0x0634 }, /*                Arabic_sheen  ARABIC LETTER SHEEN */
+		{ 0x05d5, 0x0635 }, /*                  Arabic_sad  ARABIC LETTER SAD */
+		{ 0x05d6, 0x0636 }, /*                  Arabic_dad  ARABIC LETTER DAD */
+		{ 0x05d7, 0x0637 }, /*                  Arabic_tah  ARABIC LETTER TAH */
+		{ 0x05d8, 0x0638 }, /*                  Arabic_zah  ARABIC LETTER ZAH */
+		{ 0x05d9, 0x0639 }, /*                  Arabic_ain  ARABIC LETTER AIN */
+		{ 0x05da, 0x063a }, /*                Arabic_ghain  ARABIC LETTER GHAIN */
+		{ 0x05e0, 0x0640 }, /*              Arabic_tatweel  ARABIC TATWEEL */
+		{ 0x05e1, 0x0641 }, /*                  Arabic_feh  ARABIC LETTER FEH */
+		{ 0x05e2, 0x0642 }, /*                  Arabic_qaf  ARABIC LETTER QAF */
+		{ 0x05e3, 0x0643 }, /*                  Arabic_kaf  ARABIC LETTER KAF */
+		{ 0x05e4, 0x0644 }, /*                  Arabic_lam  ARABIC LETTER LAM */
+		{ 0x05e5, 0x0645 }, /*                 Arabic_meem  ARABIC LETTER MEEM */
+		{ 0x05e6, 0x0646 }, /*                 Arabic_noon  ARABIC LETTER NOON */
+		{ 0x05e7, 0x0647 }, /*                   Arabic_ha  ARABIC LETTER HEH */
+		{ 0x05e8, 0x0648 }, /*                  Arabic_waw  ARABIC LETTER WAW */
+		{ 0x05e9, 0x0649 }, /*          Arabic_alefmaksura  ARABIC LETTER ALEF MAKSURA */
+		{ 0x05ea, 0x064a }, /*                  Arabic_yeh  ARABIC LETTER YEH */
+		{ 0x05eb, 0x064b }, /*             Arabic_fathatan  ARABIC FATHATAN */
+		{ 0x05ec, 0x064c }, /*             Arabic_dammatan  ARABIC DAMMATAN */
+		{ 0x05ed, 0x064d }, /*             Arabic_kasratan  ARABIC KASRATAN */
+		{ 0x05ee, 0x064e }, /*                Arabic_fatha  ARABIC FATHA */
+		{ 0x05ef, 0x064f }, /*                Arabic_damma  ARABIC DAMMA */
+		{ 0x05f0, 0x0650 }, /*                Arabic_kasra  ARABIC KASRA */
+		{ 0x05f1, 0x0651 }, /*               Arabic_shadda  ARABIC SHADDA */
+		{ 0x05f2, 0x0652 }, /*                Arabic_sukun  ARABIC SUKUN */
+		{ 0x06a1, 0x0452 }, /*                 Serbian_dje  CYRILLIC SMALL LETTER DJE */
+		{ 0x06a2, 0x0453 }, /*               Macedonia_gje  CYRILLIC SMALL LETTER GJE */
+		{ 0x06a3, 0x0451 }, /*                 Cyrillic_io  CYRILLIC SMALL LETTER IO */
+		{ 0x06a4, 0x0454 }, /*                Ukrainian_ie  CYRILLIC SMALL LETTER UKRAINIAN IE */
+		{ 0x06a5, 0x0455 }, /*               Macedonia_dse  CYRILLIC SMALL LETTER DZE */
+		{ 0x06a6, 0x0456 }, /*                 Ukrainian_i  CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+		{ 0x06a7, 0x0457 }, /*                Ukrainian_yi  CYRILLIC SMALL LETTER YI */
+		{ 0x06a8, 0x0458 }, /*                 Cyrillic_je  CYRILLIC SMALL LETTER JE */
+		{ 0x06a9, 0x0459 }, /*                Cyrillic_lje  CYRILLIC SMALL LETTER LJE */
+		{ 0x06aa, 0x045a }, /*                Cyrillic_nje  CYRILLIC SMALL LETTER NJE */
+		{ 0x06ab, 0x045b }, /*                Serbian_tshe  CYRILLIC SMALL LETTER TSHE */
+		{ 0x06ac, 0x045c }, /*               Macedonia_kje  CYRILLIC SMALL LETTER KJE */
+		{ 0x06ae, 0x045e }, /*         Byelorussian_shortu  CYRILLIC SMALL LETTER SHORT U */
+		{ 0x06af, 0x045f }, /*               Cyrillic_dzhe  CYRILLIC SMALL LETTER DZHE */
+		{ 0x06b0, 0x2116 }, /*                  numerosign  NUMERO SIGN */
+		{ 0x06b1, 0x0402 }, /*                 Serbian_DJE  CYRILLIC CAPITAL LETTER DJE */
+		{ 0x06b2, 0x0403 }, /*               Macedonia_GJE  CYRILLIC CAPITAL LETTER GJE */
+		{ 0x06b3, 0x0401 }, /*                 Cyrillic_IO  CYRILLIC CAPITAL LETTER IO */
+		{ 0x06b4, 0x0404 }, /*                Ukrainian_IE  CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+		{ 0x06b5, 0x0405 }, /*               Macedonia_DSE  CYRILLIC CAPITAL LETTER DZE */
+		{ 0x06b6, 0x0406 }, /*                 Ukrainian_I  CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+		{ 0x06b7, 0x0407 }, /*                Ukrainian_YI  CYRILLIC CAPITAL LETTER YI */
+		{ 0x06b8, 0x0408 }, /*                 Cyrillic_JE  CYRILLIC CAPITAL LETTER JE */
+		{ 0x06b9, 0x0409 }, /*                Cyrillic_LJE  CYRILLIC CAPITAL LETTER LJE */
+		{ 0x06ba, 0x040a }, /*                Cyrillic_NJE  CYRILLIC CAPITAL LETTER NJE */
+		{ 0x06bb, 0x040b }, /*                Serbian_TSHE  CYRILLIC CAPITAL LETTER TSHE */
+		{ 0x06bc, 0x040c }, /*               Macedonia_KJE  CYRILLIC CAPITAL LETTER KJE */
+		{ 0x06be, 0x040e }, /*         Byelorussian_SHORTU  CYRILLIC CAPITAL LETTER SHORT U */
+		{ 0x06bf, 0x040f }, /*               Cyrillic_DZHE  CYRILLIC CAPITAL LETTER DZHE */
+		{ 0x06c0, 0x044e }, /*                 Cyrillic_yu  CYRILLIC SMALL LETTER YU */
+		{ 0x06c1, 0x0430 }, /*                  Cyrillic_a  CYRILLIC SMALL LETTER A */
+		{ 0x06c2, 0x0431 }, /*                 Cyrillic_be  CYRILLIC SMALL LETTER BE */
+		{ 0x06c3, 0x0446 }, /*                Cyrillic_tse  CYRILLIC SMALL LETTER TSE */
+		{ 0x06c4, 0x0434 }, /*                 Cyrillic_de  CYRILLIC SMALL LETTER DE */
+		{ 0x06c5, 0x0435 }, /*                 Cyrillic_ie  CYRILLIC SMALL LETTER IE */
+		{ 0x06c6, 0x0444 }, /*                 Cyrillic_ef  CYRILLIC SMALL LETTER EF */
+		{ 0x06c7, 0x0433 }, /*                Cyrillic_ghe  CYRILLIC SMALL LETTER GHE */
+		{ 0x06c8, 0x0445 }, /*                 Cyrillic_ha  CYRILLIC SMALL LETTER HA */
+		{ 0x06c9, 0x0438 }, /*                  Cyrillic_i  CYRILLIC SMALL LETTER I */
+		{ 0x06ca, 0x0439 }, /*             Cyrillic_shorti  CYRILLIC SMALL LETTER SHORT I */
+		{ 0x06cb, 0x043a }, /*                 Cyrillic_ka  CYRILLIC SMALL LETTER KA */
+		{ 0x06cc, 0x043b }, /*                 Cyrillic_el  CYRILLIC SMALL LETTER EL */
+		{ 0x06cd, 0x043c }, /*                 Cyrillic_em  CYRILLIC SMALL LETTER EM */
+		{ 0x06ce, 0x043d }, /*                 Cyrillic_en  CYRILLIC SMALL LETTER EN */
+		{ 0x06cf, 0x043e }, /*                  Cyrillic_o  CYRILLIC SMALL LETTER O */
+		{ 0x06d0, 0x043f }, /*                 Cyrillic_pe  CYRILLIC SMALL LETTER PE */
+		{ 0x06d1, 0x044f }, /*                 Cyrillic_ya  CYRILLIC SMALL LETTER YA */
+		{ 0x06d2, 0x0440 }, /*                 Cyrillic_er  CYRILLIC SMALL LETTER ER */
+		{ 0x06d3, 0x0441 }, /*                 Cyrillic_es  CYRILLIC SMALL LETTER ES */
+		{ 0x06d4, 0x0442 }, /*                 Cyrillic_te  CYRILLIC SMALL LETTER TE */
+		{ 0x06d5, 0x0443 }, /*                  Cyrillic_u  CYRILLIC SMALL LETTER U */
+		{ 0x06d6, 0x0436 }, /*                Cyrillic_zhe  CYRILLIC SMALL LETTER ZHE */
+		{ 0x06d7, 0x0432 }, /*                 Cyrillic_ve  CYRILLIC SMALL LETTER VE */
+		{ 0x06d8, 0x044c }, /*           Cyrillic_softsign  CYRILLIC SMALL LETTER SOFT SIGN */
+		{ 0x06d9, 0x044b }, /*               Cyrillic_yeru  CYRILLIC SMALL LETTER YERU */
+		{ 0x06da, 0x0437 }, /*                 Cyrillic_ze  CYRILLIC SMALL LETTER ZE */
+		{ 0x06db, 0x0448 }, /*                Cyrillic_sha  CYRILLIC SMALL LETTER SHA */
+		{ 0x06dc, 0x044d }, /*                  Cyrillic_e  CYRILLIC SMALL LETTER E */
+		{ 0x06dd, 0x0449 }, /*              Cyrillic_shcha  CYRILLIC SMALL LETTER SHCHA */
+		{ 0x06de, 0x0447 }, /*                Cyrillic_che  CYRILLIC SMALL LETTER CHE */
+		{ 0x06df, 0x044a }, /*           Cyrillic_hardsign  CYRILLIC SMALL LETTER HARD SIGN */
+		{ 0x06e0, 0x042e }, /*                 Cyrillic_YU  CYRILLIC CAPITAL LETTER YU */
+		{ 0x06e1, 0x0410 }, /*                  Cyrillic_A  CYRILLIC CAPITAL LETTER A */
+		{ 0x06e2, 0x0411 }, /*                 Cyrillic_BE  CYRILLIC CAPITAL LETTER BE */
+		{ 0x06e3, 0x0426 }, /*                Cyrillic_TSE  CYRILLIC CAPITAL LETTER TSE */
+		{ 0x06e4, 0x0414 }, /*                 Cyrillic_DE  CYRILLIC CAPITAL LETTER DE */
+		{ 0x06e5, 0x0415 }, /*                 Cyrillic_IE  CYRILLIC CAPITAL LETTER IE */
+		{ 0x06e6, 0x0424 }, /*                 Cyrillic_EF  CYRILLIC CAPITAL LETTER EF */
+		{ 0x06e7, 0x0413 }, /*                Cyrillic_GHE  CYRILLIC CAPITAL LETTER GHE */
+		{ 0x06e8, 0x0425 }, /*                 Cyrillic_HA  CYRILLIC CAPITAL LETTER HA */
+		{ 0x06e9, 0x0418 }, /*                  Cyrillic_I  CYRILLIC CAPITAL LETTER I */
+		{ 0x06ea, 0x0419 }, /*             Cyrillic_SHORTI  CYRILLIC CAPITAL LETTER SHORT I */
+		{ 0x06eb, 0x041a }, /*                 Cyrillic_KA  CYRILLIC CAPITAL LETTER KA */
+		{ 0x06ec, 0x041b }, /*                 Cyrillic_EL  CYRILLIC CAPITAL LETTER EL */
+		{ 0x06ed, 0x041c }, /*                 Cyrillic_EM  CYRILLIC CAPITAL LETTER EM */
+		{ 0x06ee, 0x041d }, /*                 Cyrillic_EN  CYRILLIC CAPITAL LETTER EN */
+		{ 0x06ef, 0x041e }, /*                  Cyrillic_O  CYRILLIC CAPITAL LETTER O */
+		{ 0x06f0, 0x041f }, /*                 Cyrillic_PE  CYRILLIC CAPITAL LETTER PE */
+		{ 0x06f1, 0x042f }, /*                 Cyrillic_YA  CYRILLIC CAPITAL LETTER YA */
+		{ 0x06f2, 0x0420 }, /*                 Cyrillic_ER  CYRILLIC CAPITAL LETTER ER */
+		{ 0x06f3, 0x0421 }, /*                 Cyrillic_ES  CYRILLIC CAPITAL LETTER ES */
+		{ 0x06f4, 0x0422 }, /*                 Cyrillic_TE  CYRILLIC CAPITAL LETTER TE */
+		{ 0x06f5, 0x0423 }, /*                  Cyrillic_U  CYRILLIC CAPITAL LETTER U */
+		{ 0x06f6, 0x0416 }, /*                Cyrillic_ZHE  CYRILLIC CAPITAL LETTER ZHE */
+		{ 0x06f7, 0x0412 }, /*                 Cyrillic_VE  CYRILLIC CAPITAL LETTER VE */
+		{ 0x06f8, 0x042c }, /*           Cyrillic_SOFTSIGN  CYRILLIC CAPITAL LETTER SOFT SIGN */
+		{ 0x06f9, 0x042b }, /*               Cyrillic_YERU  CYRILLIC CAPITAL LETTER YERU */
+		{ 0x06fa, 0x0417 }, /*                 Cyrillic_ZE  CYRILLIC CAPITAL LETTER ZE */
+		{ 0x06fb, 0x0428 }, /*                Cyrillic_SHA  CYRILLIC CAPITAL LETTER SHA */
+		{ 0x06fc, 0x042d }, /*                  Cyrillic_E  CYRILLIC CAPITAL LETTER E */
+		{ 0x06fd, 0x0429 }, /*              Cyrillic_SHCHA  CYRILLIC CAPITAL LETTER SHCHA */
+		{ 0x06fe, 0x0427 }, /*                Cyrillic_CHE  CYRILLIC CAPITAL LETTER CHE */
+		{ 0x06ff, 0x042a }, /*           Cyrillic_HARDSIGN  CYRILLIC CAPITAL LETTER HARD SIGN */
+		{ 0x07a1, 0x0386 }, /*           Greek_ALPHAaccent  GREEK CAPITAL LETTER ALPHA WITH TONOS */
+		{ 0x07a2, 0x0388 }, /*         Greek_EPSILONaccent  GREEK CAPITAL LETTER EPSILON WITH TONOS */
+		{ 0x07a3, 0x0389 }, /*             Greek_ETAaccent  GREEK CAPITAL LETTER ETA WITH TONOS */
+		{ 0x07a4, 0x038a }, /*            Greek_IOTAaccent  GREEK CAPITAL LETTER IOTA WITH TONOS */
+		{ 0x07a5, 0x03aa }, /*         Greek_IOTAdiaeresis  GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+		{ 0x07a7, 0x038c }, /*         Greek_OMICRONaccent  GREEK CAPITAL LETTER OMICRON WITH TONOS */
+		{ 0x07a8, 0x038e }, /*         Greek_UPSILONaccent  GREEK CAPITAL LETTER UPSILON WITH TONOS */
+		{ 0x07a9, 0x03ab }, /*       Greek_UPSILONdieresis  GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+		{ 0x07ab, 0x038f }, /*           Greek_OMEGAaccent  GREEK CAPITAL LETTER OMEGA WITH TONOS */
+		{ 0x07ae, 0x0385 }, /*        Greek_accentdieresis  GREEK DIALYTIKA TONOS */
+		{ 0x07af, 0x2015 }, /*              Greek_horizbar  HORIZONTAL BAR */
+		{ 0x07b1, 0x03ac }, /*           Greek_alphaaccent  GREEK SMALL LETTER ALPHA WITH TONOS */
+		{ 0x07b2, 0x03ad }, /*         Greek_epsilonaccent  GREEK SMALL LETTER EPSILON WITH TONOS */
+		{ 0x07b3, 0x03ae }, /*             Greek_etaaccent  GREEK SMALL LETTER ETA WITH TONOS */
+		{ 0x07b4, 0x03af }, /*            Greek_iotaaccent  GREEK SMALL LETTER IOTA WITH TONOS */
+		{ 0x07b5, 0x03ca }, /*          Greek_iotadieresis  GREEK SMALL LETTER IOTA WITH DIALYTIKA */
+		{ 0x07b6, 0x0390 }, /*    Greek_iotaaccentdieresis  GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
+		{ 0x07b7, 0x03cc }, /*         Greek_omicronaccent  GREEK SMALL LETTER OMICRON WITH TONOS */
+		{ 0x07b8, 0x03cd }, /*         Greek_upsilonaccent  GREEK SMALL LETTER UPSILON WITH TONOS */
+		{ 0x07b9, 0x03cb }, /*       Greek_upsilondieresis  GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
+		{ 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis  GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
+		{ 0x07bb, 0x03ce }, /*           Greek_omegaaccent  GREEK SMALL LETTER OMEGA WITH TONOS */
+		{ 0x07c1, 0x0391 }, /*                 Greek_ALPHA  GREEK CAPITAL LETTER ALPHA */
+		{ 0x07c2, 0x0392 }, /*                  Greek_BETA  GREEK CAPITAL LETTER BETA */
+		{ 0x07c3, 0x0393 }, /*                 Greek_GAMMA  GREEK CAPITAL LETTER GAMMA */
+		{ 0x07c4, 0x0394 }, /*                 Greek_DELTA  GREEK CAPITAL LETTER DELTA */
+		{ 0x07c5, 0x0395 }, /*               Greek_EPSILON  GREEK CAPITAL LETTER EPSILON */
+		{ 0x07c6, 0x0396 }, /*                  Greek_ZETA  GREEK CAPITAL LETTER ZETA */
+		{ 0x07c7, 0x0397 }, /*                   Greek_ETA  GREEK CAPITAL LETTER ETA */
+		{ 0x07c8, 0x0398 }, /*                 Greek_THETA  GREEK CAPITAL LETTER THETA */
+		{ 0x07c9, 0x0399 }, /*                  Greek_IOTA  GREEK CAPITAL LETTER IOTA */
+		{ 0x07ca, 0x039a }, /*                 Greek_KAPPA  GREEK CAPITAL LETTER KAPPA */
+		{ 0x07cb, 0x039b }, /*                Greek_LAMBDA  GREEK CAPITAL LETTER LAMDA */
+		{ 0x07cc, 0x039c }, /*                    Greek_MU  GREEK CAPITAL LETTER MU */
+		{ 0x07cd, 0x039d }, /*                    Greek_NU  GREEK CAPITAL LETTER NU */
+		{ 0x07ce, 0x039e }, /*                    Greek_XI  GREEK CAPITAL LETTER XI */
+		{ 0x07cf, 0x039f }, /*               Greek_OMICRON  GREEK CAPITAL LETTER OMICRON */
+		{ 0x07d0, 0x03a0 }, /*                    Greek_PI  GREEK CAPITAL LETTER PI */
+		{ 0x07d1, 0x03a1 }, /*                   Greek_RHO  GREEK CAPITAL LETTER RHO */
+		{ 0x07d2, 0x03a3 }, /*                 Greek_SIGMA  GREEK CAPITAL LETTER SIGMA */
+		{ 0x07d4, 0x03a4 }, /*                   Greek_TAU  GREEK CAPITAL LETTER TAU */
+		{ 0x07d5, 0x03a5 }, /*               Greek_UPSILON  GREEK CAPITAL LETTER UPSILON */
+		{ 0x07d6, 0x03a6 }, /*                   Greek_PHI  GREEK CAPITAL LETTER PHI */
+		{ 0x07d7, 0x03a7 }, /*                   Greek_CHI  GREEK CAPITAL LETTER CHI */
+		{ 0x07d8, 0x03a8 }, /*                   Greek_PSI  GREEK CAPITAL LETTER PSI */
+		{ 0x07d9, 0x03a9 }, /*                 Greek_OMEGA  GREEK CAPITAL LETTER OMEGA */
+		{ 0x07e1, 0x03b1 }, /*                 Greek_alpha  GREEK SMALL LETTER ALPHA */
+		{ 0x07e2, 0x03b2 }, /*                  Greek_beta  GREEK SMALL LETTER BETA */
+		{ 0x07e3, 0x03b3 }, /*                 Greek_gamma  GREEK SMALL LETTER GAMMA */
+		{ 0x07e4, 0x03b4 }, /*                 Greek_delta  GREEK SMALL LETTER DELTA */
+		{ 0x07e5, 0x03b5 }, /*               Greek_epsilon  GREEK SMALL LETTER EPSILON */
+		{ 0x07e6, 0x03b6 }, /*                  Greek_zeta  GREEK SMALL LETTER ZETA */
+		{ 0x07e7, 0x03b7 }, /*                   Greek_eta  GREEK SMALL LETTER ETA */
+		{ 0x07e8, 0x03b8 }, /*                 Greek_theta  GREEK SMALL LETTER THETA */
+		{ 0x07e9, 0x03b9 }, /*                  Greek_iota  GREEK SMALL LETTER IOTA */
+		{ 0x07ea, 0x03ba }, /*                 Greek_kappa  GREEK SMALL LETTER KAPPA */
+		{ 0x07eb, 0x03bb }, /*                Greek_lambda  GREEK SMALL LETTER LAMDA */
+		{ 0x07ec, 0x03bc }, /*                    Greek_mu  GREEK SMALL LETTER MU */
+		{ 0x07ed, 0x03bd }, /*                    Greek_nu  GREEK SMALL LETTER NU */
+		{ 0x07ee, 0x03be }, /*                    Greek_xi  GREEK SMALL LETTER XI */
+		{ 0x07ef, 0x03bf }, /*               Greek_omicron  GREEK SMALL LETTER OMICRON */
+		{ 0x07f0, 0x03c0 }, /*                    Greek_pi  GREEK SMALL LETTER PI */
+		{ 0x07f1, 0x03c1 }, /*                   Greek_rho  GREEK SMALL LETTER RHO */
+		{ 0x07f2, 0x03c3 }, /*                 Greek_sigma  GREEK SMALL LETTER SIGMA */
+		{ 0x07f3, 0x03c2 }, /*       Greek_finalsmallsigma  GREEK SMALL LETTER FINAL SIGMA */
+		{ 0x07f4, 0x03c4 }, /*                   Greek_tau  GREEK SMALL LETTER TAU */
+		{ 0x07f5, 0x03c5 }, /*               Greek_upsilon  GREEK SMALL LETTER UPSILON */
+		{ 0x07f6, 0x03c6 }, /*                   Greek_phi  GREEK SMALL LETTER PHI */
+		{ 0x07f7, 0x03c7 }, /*                   Greek_chi  GREEK SMALL LETTER CHI */
+		{ 0x07f8, 0x03c8 }, /*                   Greek_psi  GREEK SMALL LETTER PSI */
+		{ 0x07f9, 0x03c9 }, /*                 Greek_omega  GREEK SMALL LETTER OMEGA */
+		{ 0x08a1, 0x23b7 }, /*                 leftradical  ??? */
+		{ 0x08a2, 0x250c }, /*              topleftradical  BOX DRAWINGS LIGHT DOWN AND RIGHT */
+		{ 0x08a3, 0x2500 }, /*              horizconnector  BOX DRAWINGS LIGHT HORIZONTAL */
+		{ 0x08a4, 0x2320 }, /*                 topintegral  TOP HALF INTEGRAL */
+		{ 0x08a5, 0x2321 }, /*                 botintegral  BOTTOM HALF INTEGRAL */
+		{ 0x08a6, 0x2502 }, /*               vertconnector  BOX DRAWINGS LIGHT VERTICAL */
+		{ 0x08a7, 0x23a1 }, /*            topleftsqbracket  ??? */
+		{ 0x08a8, 0x23a3 }, /*            botleftsqbracket  ??? */
+		{ 0x08a9, 0x23a4 }, /*           toprightsqbracket  ??? */
+		{ 0x08aa, 0x23a6 }, /*           botrightsqbracket  ??? */
+		{ 0x08ab, 0x239b }, /*               topleftparens  ??? */
+		{ 0x08ac, 0x239d }, /*               botleftparens  ??? */
+		{ 0x08ad, 0x239e }, /*              toprightparens  ??? */
+		{ 0x08ae, 0x23a0 }, /*              botrightparens  ??? */
+		{ 0x08af, 0x23a8 }, /*        leftmiddlecurlybrace  ??? */
+		{ 0x08b0, 0x23ac }, /*       rightmiddlecurlybrace  ??? */
+		/*  0x08b1                          topleftsummation  ??? */
+		/*  0x08b2                          botleftsummation  ??? */
+		/*  0x08b3                 topvertsummationconnector  ??? */
+		/*  0x08b4                 botvertsummationconnector  ??? */
+		/*  0x08b5                         toprightsummation  ??? */
+		/*  0x08b6                         botrightsummation  ??? */
+		/*  0x08b7                      rightmiddlesummation  ??? */
+		{ 0x08bc, 0x2264 }, /*               lessthanequal  LESS-THAN OR EQUAL TO */
+		{ 0x08bd, 0x2260 }, /*                    notequal  NOT EQUAL TO */
+		{ 0x08be, 0x2265 }, /*            greaterthanequal  GREATER-THAN OR EQUAL TO */
+		{ 0x08bf, 0x222b }, /*                    integral  INTEGRAL */
+		{ 0x08c0, 0x2234 }, /*                   therefore  THEREFORE */
+		{ 0x08c1, 0x221d }, /*                   variation  PROPORTIONAL TO */
+		{ 0x08c2, 0x221e }, /*                    infinity  INFINITY */
+		{ 0x08c5, 0x2207 }, /*                       nabla  NABLA */
+		{ 0x08c8, 0x223c }, /*                 approximate  TILDE OPERATOR */
+		{ 0x08c9, 0x2243 }, /*                similarequal  ASYMPTOTICALLY EQUAL TO */
+		{ 0x08cd, 0x21d4 }, /*                    ifonlyif  LEFT RIGHT DOUBLE ARROW */
+		{ 0x08ce, 0x21d2 }, /*                     implies  RIGHTWARDS DOUBLE ARROW */
+		{ 0x08cf, 0x2261 }, /*                   identical  IDENTICAL TO */
+		{ 0x08d6, 0x221a }, /*                     radical  SQUARE ROOT */
+		{ 0x08da, 0x2282 }, /*                  includedin  SUBSET OF */
+		{ 0x08db, 0x2283 }, /*                    includes  SUPERSET OF */
+		{ 0x08dc, 0x2229 }, /*                intersection  INTERSECTION */
+		{ 0x08dd, 0x222a }, /*                       union  UNION */
+		{ 0x08de, 0x2227 }, /*                  logicaland  LOGICAL AND */
+		{ 0x08df, 0x2228 }, /*                   logicalor  LOGICAL OR */
+		{ 0x08ef, 0x2202 }, /*           partialderivative  PARTIAL DIFFERENTIAL */
+		{ 0x08f6, 0x0192 }, /*                    function  LATIN SMALL LETTER F WITH HOOK */
+		{ 0x08fb, 0x2190 }, /*                   leftarrow  LEFTWARDS ARROW */
+		{ 0x08fc, 0x2191 }, /*                     uparrow  UPWARDS ARROW */
+		{ 0x08fd, 0x2192 }, /*                  rightarrow  RIGHTWARDS ARROW */
+		{ 0x08fe, 0x2193 }, /*                   downarrow  DOWNWARDS ARROW */
+		/*  0x09df                                     blank  ??? */
+		{ 0x09e0, 0x25c6 }, /*                soliddiamond  BLACK DIAMOND */
+		{ 0x09e1, 0x2592 }, /*                checkerboard  MEDIUM SHADE */
+		{ 0x09e2, 0x2409 }, /*                          ht  SYMBOL FOR HORIZONTAL TABULATION */
+		{ 0x09e3, 0x240c }, /*                          ff  SYMBOL FOR FORM FEED */
+		{ 0x09e4, 0x240d }, /*                          cr  SYMBOL FOR CARRIAGE RETURN */
+		{ 0x09e5, 0x240a }, /*                          lf  SYMBOL FOR LINE FEED */
+		{ 0x09e8, 0x2424 }, /*                          nl  SYMBOL FOR NEWLINE */
+		{ 0x09e9, 0x240b }, /*                          vt  SYMBOL FOR VERTICAL TABULATION */
+		{ 0x09ea, 0x2518 }, /*              lowrightcorner  BOX DRAWINGS LIGHT UP AND LEFT */
+		{ 0x09eb, 0x2510 }, /*               uprightcorner  BOX DRAWINGS LIGHT DOWN AND LEFT */
+		{ 0x09ec, 0x250c }, /*                upleftcorner  BOX DRAWINGS LIGHT DOWN AND RIGHT */
+		{ 0x09ed, 0x2514 }, /*               lowleftcorner  BOX DRAWINGS LIGHT UP AND RIGHT */
+		{ 0x09ee, 0x253c }, /*               crossinglines  BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+		{ 0x09ef, 0x23ba }, /*              horizlinescan1  HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
+		{ 0x09f0, 0x23bb }, /*              horizlinescan3  HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
+		{ 0x09f1, 0x2500 }, /*              horizlinescan5  BOX DRAWINGS LIGHT HORIZONTAL */
+		{ 0x09f2, 0x23bc }, /*              horizlinescan7  HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
+		{ 0x09f3, 0x23bd }, /*              horizlinescan9  HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
+		{ 0x09f4, 0x251c }, /*                       leftt  BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+		{ 0x09f5, 0x2524 }, /*                      rightt  BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+		{ 0x09f6, 0x2534 }, /*                        bott  BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+		{ 0x09f7, 0x252c }, /*                        topt  BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+		{ 0x09f8, 0x2502 }, /*                     vertbar  BOX DRAWINGS LIGHT VERTICAL */
+		{ 0x0aa1, 0x2003 }, /*                     emspace  EM SPACE */
+		{ 0x0aa2, 0x2002 }, /*                     enspace  EN SPACE */
+		{ 0x0aa3, 0x2004 }, /*                    em3space  THREE-PER-EM SPACE */
+		{ 0x0aa4, 0x2005 }, /*                    em4space  FOUR-PER-EM SPACE */
+		{ 0x0aa5, 0x2007 }, /*                  digitspace  FIGURE SPACE */
+		{ 0x0aa6, 0x2008 }, /*                  punctspace  PUNCTUATION SPACE */
+		{ 0x0aa7, 0x2009 }, /*                   thinspace  THIN SPACE */
+		{ 0x0aa8, 0x200a }, /*                   hairspace  HAIR SPACE */
+		{ 0x0aa9, 0x2014 }, /*                      emdash  EM DASH */
+		{ 0x0aaa, 0x2013 }, /*                      endash  EN DASH */
+		/*  0x0aac                               signifblank  ??? */
+		{ 0x0aae, 0x2026 }, /*                    ellipsis  HORIZONTAL ELLIPSIS */
+		{ 0x0aaf, 0x2025 }, /*             doubbaselinedot  TWO DOT LEADER */
+		{ 0x0ab0, 0x2153 }, /*                    onethird  VULGAR FRACTION ONE THIRD */
+		{ 0x0ab1, 0x2154 }, /*                   twothirds  VULGAR FRACTION TWO THIRDS */
+		{ 0x0ab2, 0x2155 }, /*                    onefifth  VULGAR FRACTION ONE FIFTH */
+		{ 0x0ab3, 0x2156 }, /*                   twofifths  VULGAR FRACTION TWO FIFTHS */
+		{ 0x0ab4, 0x2157 }, /*                 threefifths  VULGAR FRACTION THREE FIFTHS */
+		{ 0x0ab5, 0x2158 }, /*                  fourfifths  VULGAR FRACTION FOUR FIFTHS */
+		{ 0x0ab6, 0x2159 }, /*                    onesixth  VULGAR FRACTION ONE SIXTH */
+		{ 0x0ab7, 0x215a }, /*                  fivesixths  VULGAR FRACTION FIVE SIXTHS */
+		{ 0x0ab8, 0x2105 }, /*                      careof  CARE OF */
+		{ 0x0abb, 0x2012 }, /*                     figdash  FIGURE DASH */
+		{ 0x0abc, 0x2329 }, /*            leftanglebracket  LEFT-POINTING ANGLE BRACKET */
+		/*  0x0abd                              decimalpoint  ??? */
+		{ 0x0abe, 0x232a }, /*           rightanglebracket  RIGHT-POINTING ANGLE BRACKET */
+		/*  0x0abf                                    marker  ??? */
+		{ 0x0ac3, 0x215b }, /*                   oneeighth  VULGAR FRACTION ONE EIGHTH */
+		{ 0x0ac4, 0x215c }, /*                threeeighths  VULGAR FRACTION THREE EIGHTHS */
+		{ 0x0ac5, 0x215d }, /*                 fiveeighths  VULGAR FRACTION FIVE EIGHTHS */
+		{ 0x0ac6, 0x215e }, /*                seveneighths  VULGAR FRACTION SEVEN EIGHTHS */
+		{ 0x0ac9, 0x2122 }, /*                   trademark  TRADE MARK SIGN */
+		{ 0x0aca, 0x2613 }, /*               signaturemark  SALTIRE */
+		/*  0x0acb                         trademarkincircle  ??? */
+		{ 0x0acc, 0x25c1 }, /*            leftopentriangle  WHITE LEFT-POINTING TRIANGLE */
+		{ 0x0acd, 0x25b7 }, /*           rightopentriangle  WHITE RIGHT-POINTING TRIANGLE */
+		{ 0x0ace, 0x25cb }, /*                emopencircle  WHITE CIRCLE */
+		{ 0x0acf, 0x25af }, /*             emopenrectangle  WHITE VERTICAL RECTANGLE */
+		{ 0x0ad0, 0x2018 }, /*         leftsinglequotemark  LEFT SINGLE QUOTATION MARK */
+		{ 0x0ad1, 0x2019 }, /*        rightsinglequotemark  RIGHT SINGLE QUOTATION MARK */
+		{ 0x0ad2, 0x201c }, /*         leftdoublequotemark  LEFT DOUBLE QUOTATION MARK */
+		{ 0x0ad3, 0x201d }, /*        rightdoublequotemark  RIGHT DOUBLE QUOTATION MARK */
+		{ 0x0ad4, 0x211e }, /*                prescription  PRESCRIPTION TAKE */
+		{ 0x0ad6, 0x2032 }, /*                     minutes  PRIME */
+		{ 0x0ad7, 0x2033 }, /*                     seconds  DOUBLE PRIME */
+		{ 0x0ad9, 0x271d }, /*                  latincross  LATIN CROSS */
+		/*  0x0ada                                  hexagram  ??? */
+		{ 0x0adb, 0x25ac }, /*            filledrectbullet  BLACK RECTANGLE */
+		{ 0x0adc, 0x25c0 }, /*         filledlefttribullet  BLACK LEFT-POINTING TRIANGLE */
+		{ 0x0add, 0x25b6 }, /*        filledrighttribullet  BLACK RIGHT-POINTING TRIANGLE */
+		{ 0x0ade, 0x25cf }, /*              emfilledcircle  BLACK CIRCLE */
+		{ 0x0adf, 0x25ae }, /*                emfilledrect  BLACK VERTICAL RECTANGLE */
+		{ 0x0ae0, 0x25e6 }, /*            enopencircbullet  WHITE BULLET */
+		{ 0x0ae1, 0x25ab }, /*          enopensquarebullet  WHITE SMALL SQUARE */
+		{ 0x0ae2, 0x25ad }, /*              openrectbullet  WHITE RECTANGLE */
+		{ 0x0ae3, 0x25b3 }, /*             opentribulletup  WHITE UP-POINTING TRIANGLE */
+		{ 0x0ae4, 0x25bd }, /*           opentribulletdown  WHITE DOWN-POINTING TRIANGLE */
+		{ 0x0ae5, 0x2606 }, /*                    openstar  WHITE STAR */
+		{ 0x0ae6, 0x2022 }, /*          enfilledcircbullet  BULLET */
+		{ 0x0ae7, 0x25aa }, /*            enfilledsqbullet  BLACK SMALL SQUARE */
+		{ 0x0ae8, 0x25b2 }, /*           filledtribulletup  BLACK UP-POINTING TRIANGLE */
+		{ 0x0ae9, 0x25bc }, /*         filledtribulletdown  BLACK DOWN-POINTING TRIANGLE */
+		{ 0x0aea, 0x261c }, /*                 leftpointer  WHITE LEFT POINTING INDEX */
+		{ 0x0aeb, 0x261e }, /*                rightpointer  WHITE RIGHT POINTING INDEX */
+		{ 0x0aec, 0x2663 }, /*                        club  BLACK CLUB SUIT */
+		{ 0x0aed, 0x2666 }, /*                     diamond  BLACK DIAMOND SUIT */
+		{ 0x0aee, 0x2665 }, /*                       heart  BLACK HEART SUIT */
+		{ 0x0af0, 0x2720 }, /*                maltesecross  MALTESE CROSS */
+		{ 0x0af1, 0x2020 }, /*                      dagger  DAGGER */
+		{ 0x0af2, 0x2021 }, /*                doubledagger  DOUBLE DAGGER */
+		{ 0x0af3, 0x2713 }, /*                   checkmark  CHECK MARK */
+		{ 0x0af4, 0x2717 }, /*                 ballotcross  BALLOT X */
+		{ 0x0af5, 0x266f }, /*                musicalsharp  MUSIC SHARP SIGN */
+		{ 0x0af6, 0x266d }, /*                 musicalflat  MUSIC FLAT SIGN */
+		{ 0x0af7, 0x2642 }, /*                  malesymbol  MALE SIGN */
+		{ 0x0af8, 0x2640 }, /*                femalesymbol  FEMALE SIGN */
+		{ 0x0af9, 0x260e }, /*                   telephone  BLACK TELEPHONE */
+		{ 0x0afa, 0x2315 }, /*           telephonerecorder  TELEPHONE RECORDER */
+		{ 0x0afb, 0x2117 }, /*         phonographcopyright  SOUND RECORDING COPYRIGHT */
+		{ 0x0afc, 0x2038 }, /*                       caret  CARET */
+		{ 0x0afd, 0x201a }, /*          singlelowquotemark  SINGLE LOW-9 QUOTATION MARK */
+		{ 0x0afe, 0x201e }, /*          doublelowquotemark  DOUBLE LOW-9 QUOTATION MARK */
+		/*  0x0aff                                    cursor  ??? */
+		{ 0x0ba3, 0x003c }, /*                   leftcaret  LESS-THAN SIGN */
+		{ 0x0ba6, 0x003e }, /*                  rightcaret  GREATER-THAN SIGN */
+		{ 0x0ba8, 0x2228 }, /*                   downcaret  LOGICAL OR */
+		{ 0x0ba9, 0x2227 }, /*                     upcaret  LOGICAL AND */
+		{ 0x0bc0, 0x00af }, /*                     overbar  MACRON */
+		{ 0x0bc2, 0x22a5 }, /*                    downtack  UP TACK */
+		{ 0x0bc3, 0x2229 }, /*                      upshoe  INTERSECTION */
+		{ 0x0bc4, 0x230a }, /*                   downstile  LEFT FLOOR */
+		{ 0x0bc6, 0x005f }, /*                    underbar  LOW LINE */
+		{ 0x0bca, 0x2218 }, /*                         jot  RING OPERATOR */
+		{ 0x0bcc, 0x2395 }, /*                        quad  APL FUNCTIONAL SYMBOL QUAD */
+		{ 0x0bce, 0x22a4 }, /*                      uptack  DOWN TACK */
+		{ 0x0bcf, 0x25cb }, /*                      circle  WHITE CIRCLE */
+		{ 0x0bd3, 0x2308 }, /*                     upstile  LEFT CEILING */
+		{ 0x0bd6, 0x222a }, /*                    downshoe  UNION */
+		{ 0x0bd8, 0x2283 }, /*                   rightshoe  SUPERSET OF */
+		{ 0x0bda, 0x2282 }, /*                    leftshoe  SUBSET OF */
+		{ 0x0bdc, 0x22a2 }, /*                    lefttack  RIGHT TACK */
+		{ 0x0bfc, 0x22a3 }, /*                   righttack  LEFT TACK */
+		{ 0x0cdf, 0x2017 }, /*        hebrew_doublelowline  DOUBLE LOW LINE */
+		{ 0x0ce0, 0x05d0 }, /*                hebrew_aleph  HEBREW LETTER ALEF */
+		{ 0x0ce1, 0x05d1 }, /*                  hebrew_bet  HEBREW LETTER BET */
+		{ 0x0ce2, 0x05d2 }, /*                hebrew_gimel  HEBREW LETTER GIMEL */
+		{ 0x0ce3, 0x05d3 }, /*                hebrew_dalet  HEBREW LETTER DALET */
+		{ 0x0ce4, 0x05d4 }, /*                   hebrew_he  HEBREW LETTER HE */
+		{ 0x0ce5, 0x05d5 }, /*                  hebrew_waw  HEBREW LETTER VAV */
+		{ 0x0ce6, 0x05d6 }, /*                 hebrew_zain  HEBREW LETTER ZAYIN */
+		{ 0x0ce7, 0x05d7 }, /*                 hebrew_chet  HEBREW LETTER HET */
+		{ 0x0ce8, 0x05d8 }, /*                  hebrew_tet  HEBREW LETTER TET */
+		{ 0x0ce9, 0x05d9 }, /*                  hebrew_yod  HEBREW LETTER YOD */
+		{ 0x0cea, 0x05da }, /*            hebrew_finalkaph  HEBREW LETTER FINAL KAF */
+		{ 0x0ceb, 0x05db }, /*                 hebrew_kaph  HEBREW LETTER KAF */
+		{ 0x0cec, 0x05dc }, /*                hebrew_lamed  HEBREW LETTER LAMED */
+		{ 0x0ced, 0x05dd }, /*             hebrew_finalmem  HEBREW LETTER FINAL MEM */
+		{ 0x0cee, 0x05de }, /*                  hebrew_mem  HEBREW LETTER MEM */
+		{ 0x0cef, 0x05df }, /*             hebrew_finalnun  HEBREW LETTER FINAL NUN */
+		{ 0x0cf0, 0x05e0 }, /*                  hebrew_nun  HEBREW LETTER NUN */
+		{ 0x0cf1, 0x05e1 }, /*               hebrew_samech  HEBREW LETTER SAMEKH */
+		{ 0x0cf2, 0x05e2 }, /*                 hebrew_ayin  HEBREW LETTER AYIN */
+		{ 0x0cf3, 0x05e3 }, /*              hebrew_finalpe  HEBREW LETTER FINAL PE */
+		{ 0x0cf4, 0x05e4 }, /*                   hebrew_pe  HEBREW LETTER PE */
+		{ 0x0cf5, 0x05e5 }, /*            hebrew_finalzade  HEBREW LETTER FINAL TSADI */
+		{ 0x0cf6, 0x05e6 }, /*                 hebrew_zade  HEBREW LETTER TSADI */
+		{ 0x0cf7, 0x05e7 }, /*                 hebrew_qoph  HEBREW LETTER QOF */
+		{ 0x0cf8, 0x05e8 }, /*                 hebrew_resh  HEBREW LETTER RESH */
+		{ 0x0cf9, 0x05e9 }, /*                 hebrew_shin  HEBREW LETTER SHIN */
+		{ 0x0cfa, 0x05ea }, /*                  hebrew_taw  HEBREW LETTER TAV */
+		{ 0x0da1, 0x0e01 }, /*                  Thai_kokai  THAI CHARACTER KO KAI */
+		{ 0x0da2, 0x0e02 }, /*                Thai_khokhai  THAI CHARACTER KHO KHAI */
+		{ 0x0da3, 0x0e03 }, /*               Thai_khokhuat  THAI CHARACTER KHO KHUAT */
+		{ 0x0da4, 0x0e04 }, /*               Thai_khokhwai  THAI CHARACTER KHO KHWAI */
+		{ 0x0da5, 0x0e05 }, /*                Thai_khokhon  THAI CHARACTER KHO KHON */
+		{ 0x0da6, 0x0e06 }, /*             Thai_khorakhang  THAI CHARACTER KHO RAKHANG */
+		{ 0x0da7, 0x0e07 }, /*                 Thai_ngongu  THAI CHARACTER NGO NGU */
+		{ 0x0da8, 0x0e08 }, /*                Thai_chochan  THAI CHARACTER CHO CHAN */
+		{ 0x0da9, 0x0e09 }, /*               Thai_choching  THAI CHARACTER CHO CHING */
+		{ 0x0daa, 0x0e0a }, /*               Thai_chochang  THAI CHARACTER CHO CHANG */
+		{ 0x0dab, 0x0e0b }, /*                   Thai_soso  THAI CHARACTER SO SO */
+		{ 0x0dac, 0x0e0c }, /*                Thai_chochoe  THAI CHARACTER CHO CHOE */
+		{ 0x0dad, 0x0e0d }, /*                 Thai_yoying  THAI CHARACTER YO YING */
+		{ 0x0dae, 0x0e0e }, /*                Thai_dochada  THAI CHARACTER DO CHADA */
+		{ 0x0daf, 0x0e0f }, /*                Thai_topatak  THAI CHARACTER TO PATAK */
+		{ 0x0db0, 0x0e10 }, /*                Thai_thothan  THAI CHARACTER THO THAN */
+		{ 0x0db1, 0x0e11 }, /*          Thai_thonangmontho  THAI CHARACTER THO NANGMONTHO */
+		{ 0x0db2, 0x0e12 }, /*             Thai_thophuthao  THAI CHARACTER THO PHUTHAO */
+		{ 0x0db3, 0x0e13 }, /*                  Thai_nonen  THAI CHARACTER NO NEN */
+		{ 0x0db4, 0x0e14 }, /*                  Thai_dodek  THAI CHARACTER DO DEK */
+		{ 0x0db5, 0x0e15 }, /*                  Thai_totao  THAI CHARACTER TO TAO */
+		{ 0x0db6, 0x0e16 }, /*               Thai_thothung  THAI CHARACTER THO THUNG */
+		{ 0x0db7, 0x0e17 }, /*              Thai_thothahan  THAI CHARACTER THO THAHAN */
+		{ 0x0db8, 0x0e18 }, /*               Thai_thothong  THAI CHARACTER THO THONG */
+		{ 0x0db9, 0x0e19 }, /*                   Thai_nonu  THAI CHARACTER NO NU */
+		{ 0x0dba, 0x0e1a }, /*               Thai_bobaimai  THAI CHARACTER BO BAIMAI */
+		{ 0x0dbb, 0x0e1b }, /*                  Thai_popla  THAI CHARACTER PO PLA */
+		{ 0x0dbc, 0x0e1c }, /*               Thai_phophung  THAI CHARACTER PHO PHUNG */
+		{ 0x0dbd, 0x0e1d }, /*                   Thai_fofa  THAI CHARACTER FO FA */
+		{ 0x0dbe, 0x0e1e }, /*                Thai_phophan  THAI CHARACTER PHO PHAN */
+		{ 0x0dbf, 0x0e1f }, /*                  Thai_fofan  THAI CHARACTER FO FAN */
+		{ 0x0dc0, 0x0e20 }, /*             Thai_phosamphao  THAI CHARACTER PHO SAMPHAO */
+		{ 0x0dc1, 0x0e21 }, /*                   Thai_moma  THAI CHARACTER MO MA */
+		{ 0x0dc2, 0x0e22 }, /*                  Thai_yoyak  THAI CHARACTER YO YAK */
+		{ 0x0dc3, 0x0e23 }, /*                  Thai_rorua  THAI CHARACTER RO RUA */
+		{ 0x0dc4, 0x0e24 }, /*                     Thai_ru  THAI CHARACTER RU */
+		{ 0x0dc5, 0x0e25 }, /*                 Thai_loling  THAI CHARACTER LO LING */
+		{ 0x0dc6, 0x0e26 }, /*                     Thai_lu  THAI CHARACTER LU */
+		{ 0x0dc7, 0x0e27 }, /*                 Thai_wowaen  THAI CHARACTER WO WAEN */
+		{ 0x0dc8, 0x0e28 }, /*                 Thai_sosala  THAI CHARACTER SO SALA */
+		{ 0x0dc9, 0x0e29 }, /*                 Thai_sorusi  THAI CHARACTER SO RUSI */
+		{ 0x0dca, 0x0e2a }, /*                  Thai_sosua  THAI CHARACTER SO SUA */
+		{ 0x0dcb, 0x0e2b }, /*                  Thai_hohip  THAI CHARACTER HO HIP */
+		{ 0x0dcc, 0x0e2c }, /*                Thai_lochula  THAI CHARACTER LO CHULA */
+		{ 0x0dcd, 0x0e2d }, /*                   Thai_oang  THAI CHARACTER O ANG */
+		{ 0x0dce, 0x0e2e }, /*               Thai_honokhuk  THAI CHARACTER HO NOKHUK */
+		{ 0x0dcf, 0x0e2f }, /*              Thai_paiyannoi  THAI CHARACTER PAIYANNOI */
+		{ 0x0dd0, 0x0e30 }, /*                  Thai_saraa  THAI CHARACTER SARA A */
+		{ 0x0dd1, 0x0e31 }, /*             Thai_maihanakat  THAI CHARACTER MAI HAN-AKAT */
+		{ 0x0dd2, 0x0e32 }, /*                 Thai_saraaa  THAI CHARACTER SARA AA */
+		{ 0x0dd3, 0x0e33 }, /*                 Thai_saraam  THAI CHARACTER SARA AM */
+		{ 0x0dd4, 0x0e34 }, /*                  Thai_sarai  THAI CHARACTER SARA I */
+		{ 0x0dd5, 0x0e35 }, /*                 Thai_saraii  THAI CHARACTER SARA II */
+		{ 0x0dd6, 0x0e36 }, /*                 Thai_saraue  THAI CHARACTER SARA UE */
+		{ 0x0dd7, 0x0e37 }, /*                Thai_sarauee  THAI CHARACTER SARA UEE */
+		{ 0x0dd8, 0x0e38 }, /*                  Thai_sarau  THAI CHARACTER SARA U */
+		{ 0x0dd9, 0x0e39 }, /*                 Thai_sarauu  THAI CHARACTER SARA UU */
+		{ 0x0dda, 0x0e3a }, /*                Thai_phinthu  THAI CHARACTER PHINTHU */
+		/*  0x0dde                    Thai_maihanakat_maitho  ??? */
+		{ 0x0ddf, 0x0e3f }, /*                   Thai_baht  THAI CURRENCY SYMBOL BAHT */
+		{ 0x0de0, 0x0e40 }, /*                  Thai_sarae  THAI CHARACTER SARA E */
+		{ 0x0de1, 0x0e41 }, /*                 Thai_saraae  THAI CHARACTER SARA AE */
+		{ 0x0de2, 0x0e42 }, /*                  Thai_sarao  THAI CHARACTER SARA O */
+		{ 0x0de3, 0x0e43 }, /*          Thai_saraaimaimuan  THAI CHARACTER SARA AI MAIMUAN */
+		{ 0x0de4, 0x0e44 }, /*         Thai_saraaimaimalai  THAI CHARACTER SARA AI MAIMALAI */
+		{ 0x0de5, 0x0e45 }, /*            Thai_lakkhangyao  THAI CHARACTER LAKKHANGYAO */
+		{ 0x0de6, 0x0e46 }, /*               Thai_maiyamok  THAI CHARACTER MAIYAMOK */
+		{ 0x0de7, 0x0e47 }, /*              Thai_maitaikhu  THAI CHARACTER MAITAIKHU */
+		{ 0x0de8, 0x0e48 }, /*                  Thai_maiek  THAI CHARACTER MAI EK */
+		{ 0x0de9, 0x0e49 }, /*                 Thai_maitho  THAI CHARACTER MAI THO */
+		{ 0x0dea, 0x0e4a }, /*                 Thai_maitri  THAI CHARACTER MAI TRI */
+		{ 0x0deb, 0x0e4b }, /*            Thai_maichattawa  THAI CHARACTER MAI CHATTAWA */
+		{ 0x0dec, 0x0e4c }, /*            Thai_thanthakhat  THAI CHARACTER THANTHAKHAT */
+		{ 0x0ded, 0x0e4d }, /*               Thai_nikhahit  THAI CHARACTER NIKHAHIT */
+		{ 0x0df0, 0x0e50 }, /*                 Thai_leksun  THAI DIGIT ZERO */
+		{ 0x0df1, 0x0e51 }, /*                Thai_leknung  THAI DIGIT ONE */
+		{ 0x0df2, 0x0e52 }, /*                Thai_leksong  THAI DIGIT TWO */
+		{ 0x0df3, 0x0e53 }, /*                 Thai_leksam  THAI DIGIT THREE */
+		{ 0x0df4, 0x0e54 }, /*                  Thai_leksi  THAI DIGIT FOUR */
+		{ 0x0df5, 0x0e55 }, /*                  Thai_lekha  THAI DIGIT FIVE */
+		{ 0x0df6, 0x0e56 }, /*                 Thai_lekhok  THAI DIGIT SIX */
+		{ 0x0df7, 0x0e57 }, /*                Thai_lekchet  THAI DIGIT SEVEN */
+		{ 0x0df8, 0x0e58 }, /*                Thai_lekpaet  THAI DIGIT EIGHT */
+		{ 0x0df9, 0x0e59 }, /*                 Thai_lekkao  THAI DIGIT NINE */
+		{ 0x0ea1, 0x3131 }, /*               Hangul_Kiyeog  HANGUL LETTER KIYEOK */
+		{ 0x0ea2, 0x3132 }, /*          Hangul_SsangKiyeog  HANGUL LETTER SSANGKIYEOK */
+		{ 0x0ea3, 0x3133 }, /*           Hangul_KiyeogSios  HANGUL LETTER KIYEOK-SIOS */
+		{ 0x0ea4, 0x3134 }, /*                Hangul_Nieun  HANGUL LETTER NIEUN */
+		{ 0x0ea5, 0x3135 }, /*           Hangul_NieunJieuj  HANGUL LETTER NIEUN-CIEUC */
+		{ 0x0ea6, 0x3136 }, /*           Hangul_NieunHieuh  HANGUL LETTER NIEUN-HIEUH */
+		{ 0x0ea7, 0x3137 }, /*               Hangul_Dikeud  HANGUL LETTER TIKEUT */
+		{ 0x0ea8, 0x3138 }, /*          Hangul_SsangDikeud  HANGUL LETTER SSANGTIKEUT */
+		{ 0x0ea9, 0x3139 }, /*                Hangul_Rieul  HANGUL LETTER RIEUL */
+		{ 0x0eaa, 0x313a }, /*          Hangul_RieulKiyeog  HANGUL LETTER RIEUL-KIYEOK */
+		{ 0x0eab, 0x313b }, /*           Hangul_RieulMieum  HANGUL LETTER RIEUL-MIEUM */
+		{ 0x0eac, 0x313c }, /*           Hangul_RieulPieub  HANGUL LETTER RIEUL-PIEUP */
+		{ 0x0ead, 0x313d }, /*            Hangul_RieulSios  HANGUL LETTER RIEUL-SIOS */
+		{ 0x0eae, 0x313e }, /*           Hangul_RieulTieut  HANGUL LETTER RIEUL-THIEUTH */
+		{ 0x0eaf, 0x313f }, /*          Hangul_RieulPhieuf  HANGUL LETTER RIEUL-PHIEUPH */
+		{ 0x0eb0, 0x3140 }, /*           Hangul_RieulHieuh  HANGUL LETTER RIEUL-HIEUH */
+		{ 0x0eb1, 0x3141 }, /*                Hangul_Mieum  HANGUL LETTER MIEUM */
+		{ 0x0eb2, 0x3142 }, /*                Hangul_Pieub  HANGUL LETTER PIEUP */
+		{ 0x0eb3, 0x3143 }, /*           Hangul_SsangPieub  HANGUL LETTER SSANGPIEUP */
+		{ 0x0eb4, 0x3144 }, /*            Hangul_PieubSios  HANGUL LETTER PIEUP-SIOS */
+		{ 0x0eb5, 0x3145 }, /*                 Hangul_Sios  HANGUL LETTER SIOS */
+		{ 0x0eb6, 0x3146 }, /*            Hangul_SsangSios  HANGUL LETTER SSANGSIOS */
+		{ 0x0eb7, 0x3147 }, /*                Hangul_Ieung  HANGUL LETTER IEUNG */
+		{ 0x0eb8, 0x3148 }, /*                Hangul_Jieuj  HANGUL LETTER CIEUC */
+		{ 0x0eb9, 0x3149 }, /*           Hangul_SsangJieuj  HANGUL LETTER SSANGCIEUC */
+		{ 0x0eba, 0x314a }, /*                Hangul_Cieuc  HANGUL LETTER CHIEUCH */
+		{ 0x0ebb, 0x314b }, /*               Hangul_Khieuq  HANGUL LETTER KHIEUKH */
+		{ 0x0ebc, 0x314c }, /*                Hangul_Tieut  HANGUL LETTER THIEUTH */
+		{ 0x0ebd, 0x314d }, /*               Hangul_Phieuf  HANGUL LETTER PHIEUPH */
+		{ 0x0ebe, 0x314e }, /*                Hangul_Hieuh  HANGUL LETTER HIEUH */
+		{ 0x0ebf, 0x314f }, /*                    Hangul_A  HANGUL LETTER A */
+		{ 0x0ec0, 0x3150 }, /*                   Hangul_AE  HANGUL LETTER AE */
+		{ 0x0ec1, 0x3151 }, /*                   Hangul_YA  HANGUL LETTER YA */
+		{ 0x0ec2, 0x3152 }, /*                  Hangul_YAE  HANGUL LETTER YAE */
+		{ 0x0ec3, 0x3153 }, /*                   Hangul_EO  HANGUL LETTER EO */
+		{ 0x0ec4, 0x3154 }, /*                    Hangul_E  HANGUL LETTER E */
+		{ 0x0ec5, 0x3155 }, /*                  Hangul_YEO  HANGUL LETTER YEO */
+		{ 0x0ec6, 0x3156 }, /*                   Hangul_YE  HANGUL LETTER YE */
+		{ 0x0ec7, 0x3157 }, /*                    Hangul_O  HANGUL LETTER O */
+		{ 0x0ec8, 0x3158 }, /*                   Hangul_WA  HANGUL LETTER WA */
+		{ 0x0ec9, 0x3159 }, /*                  Hangul_WAE  HANGUL LETTER WAE */
+		{ 0x0eca, 0x315a }, /*                   Hangul_OE  HANGUL LETTER OE */
+		{ 0x0ecb, 0x315b }, /*                   Hangul_YO  HANGUL LETTER YO */
+		{ 0x0ecc, 0x315c }, /*                    Hangul_U  HANGUL LETTER U */
+		{ 0x0ecd, 0x315d }, /*                  Hangul_WEO  HANGUL LETTER WEO */
+		{ 0x0ece, 0x315e }, /*                   Hangul_WE  HANGUL LETTER WE */
+		{ 0x0ecf, 0x315f }, /*                   Hangul_WI  HANGUL LETTER WI */
+		{ 0x0ed0, 0x3160 }, /*                   Hangul_YU  HANGUL LETTER YU */
+		{ 0x0ed1, 0x3161 }, /*                   Hangul_EU  HANGUL LETTER EU */
+		{ 0x0ed2, 0x3162 }, /*                   Hangul_YI  HANGUL LETTER YI */
+		{ 0x0ed3, 0x3163 }, /*                    Hangul_I  HANGUL LETTER I */
+		{ 0x0ed4, 0x11a8 }, /*             Hangul_J_Kiyeog  HANGUL JONGSEONG KIYEOK */
+		{ 0x0ed5, 0x11a9 }, /*        Hangul_J_SsangKiyeog  HANGUL JONGSEONG SSANGKIYEOK */
+		{ 0x0ed6, 0x11aa }, /*         Hangul_J_KiyeogSios  HANGUL JONGSEONG KIYEOK-SIOS */
+		{ 0x0ed7, 0x11ab }, /*              Hangul_J_Nieun  HANGUL JONGSEONG NIEUN */
+		{ 0x0ed8, 0x11ac }, /*         Hangul_J_NieunJieuj  HANGUL JONGSEONG NIEUN-CIEUC */
+		{ 0x0ed9, 0x11ad }, /*         Hangul_J_NieunHieuh  HANGUL JONGSEONG NIEUN-HIEUH */
+		{ 0x0eda, 0x11ae }, /*             Hangul_J_Dikeud  HANGUL JONGSEONG TIKEUT */
+		{ 0x0edb, 0x11af }, /*              Hangul_J_Rieul  HANGUL JONGSEONG RIEUL */
+		{ 0x0edc, 0x11b0 }, /*        Hangul_J_RieulKiyeog  HANGUL JONGSEONG RIEUL-KIYEOK */
+		{ 0x0edd, 0x11b1 }, /*         Hangul_J_RieulMieum  HANGUL JONGSEONG RIEUL-MIEUM */
+		{ 0x0ede, 0x11b2 }, /*         Hangul_J_RieulPieub  HANGUL JONGSEONG RIEUL-PIEUP */
+		{ 0x0edf, 0x11b3 }, /*          Hangul_J_RieulSios  HANGUL JONGSEONG RIEUL-SIOS */
+		{ 0x0ee0, 0x11b4 }, /*         Hangul_J_RieulTieut  HANGUL JONGSEONG RIEUL-THIEUTH */
+		{ 0x0ee1, 0x11b5 }, /*        Hangul_J_RieulPhieuf  HANGUL JONGSEONG RIEUL-PHIEUPH */
+		{ 0x0ee2, 0x11b6 }, /*         Hangul_J_RieulHieuh  HANGUL JONGSEONG RIEUL-HIEUH */
+		{ 0x0ee3, 0x11b7 }, /*              Hangul_J_Mieum  HANGUL JONGSEONG MIEUM */
+		{ 0x0ee4, 0x11b8 }, /*              Hangul_J_Pieub  HANGUL JONGSEONG PIEUP */
+		{ 0x0ee5, 0x11b9 }, /*          Hangul_J_PieubSios  HANGUL JONGSEONG PIEUP-SIOS */
+		{ 0x0ee6, 0x11ba }, /*               Hangul_J_Sios  HANGUL JONGSEONG SIOS */
+		{ 0x0ee7, 0x11bb }, /*          Hangul_J_SsangSios  HANGUL JONGSEONG SSANGSIOS */
+		{ 0x0ee8, 0x11bc }, /*              Hangul_J_Ieung  HANGUL JONGSEONG IEUNG */
+		{ 0x0ee9, 0x11bd }, /*              Hangul_J_Jieuj  HANGUL JONGSEONG CIEUC */
+		{ 0x0eea, 0x11be }, /*              Hangul_J_Cieuc  HANGUL JONGSEONG CHIEUCH */
+		{ 0x0eeb, 0x11bf }, /*             Hangul_J_Khieuq  HANGUL JONGSEONG KHIEUKH */
+		{ 0x0eec, 0x11c0 }, /*              Hangul_J_Tieut  HANGUL JONGSEONG THIEUTH */
+		{ 0x0eed, 0x11c1 }, /*             Hangul_J_Phieuf  HANGUL JONGSEONG PHIEUPH */
+		{ 0x0eee, 0x11c2 }, /*              Hangul_J_Hieuh  HANGUL JONGSEONG HIEUH */
+		{ 0x0eef, 0x316d }, /*     Hangul_RieulYeorinHieuh  HANGUL LETTER RIEUL-YEORINHIEUH */
+		{ 0x0ef0, 0x3171 }, /*    Hangul_SunkyeongeumMieum  HANGUL LETTER KAPYEOUNMIEUM */
+		{ 0x0ef1, 0x3178 }, /*    Hangul_SunkyeongeumPieub  HANGUL LETTER KAPYEOUNPIEUP */
+		{ 0x0ef2, 0x317f }, /*              Hangul_PanSios  HANGUL LETTER PANSIOS */
+		{ 0x0ef3, 0x3181 }, /*    Hangul_KkogjiDalrinIeung  HANGUL LETTER YESIEUNG */
+		{ 0x0ef4, 0x3184 }, /*   Hangul_SunkyeongeumPhieuf  HANGUL LETTER KAPYEOUNPHIEUPH */
+		{ 0x0ef5, 0x3186 }, /*          Hangul_YeorinHieuh  HANGUL LETTER YEORINHIEUH */
+		{ 0x0ef6, 0x318d }, /*                Hangul_AraeA  HANGUL LETTER ARAEA */
+		{ 0x0ef7, 0x318e }, /*               Hangul_AraeAE  HANGUL LETTER ARAEAE */
+		{ 0x0ef8, 0x11eb }, /*            Hangul_J_PanSios  HANGUL JONGSEONG PANSIOS */
+		{ 0x0ef9, 0x11f0 }, /*  Hangul_J_KkogjiDalrinIeung  HANGUL JONGSEONG YESIEUNG */
+		{ 0x0efa, 0x11f9 }, /*        Hangul_J_YeorinHieuh  HANGUL JONGSEONG YEORINHIEUH */
+		{ 0x0eff, 0x20a9 }, /*                  Korean_Won  WON SIGN */
+		{ 0x13a4, 0x20ac }, /*                        Euro  EURO SIGN */
+		{ 0x13bc, 0x0152 }, /*                          OE  LATIN CAPITAL LIGATURE OE */
+		{ 0x13bd, 0x0153 }, /*                          oe  LATIN SMALL LIGATURE OE */
+		{ 0x13be, 0x0178 }, /*                  Ydiaeresis  LATIN CAPITAL LETTER Y WITH DIAERESIS */
+		{ 0x20ac, 0x20ac }, /*                    EuroSign  EURO SIGN */
+	});
+
+	private static Map<Integer, Integer> toMap(int[][] keys) {
+		Map<Integer, Integer> keyMap = new HashMap<Integer, Integer>();
+		for (int[] km: keys) {
+			keyMap.put(km[1], km[0]);
+		}
+		return keyMap;
+	}
+
+	public static int unicode2keysym(int ch) {
+		if (ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255)
+			return ch;
+		Integer converted = keyMap.get(ch);
+		return converted != null ?
+				converted :
+				// No variants has been found for the unicode symbol then pass as unicode
+				// with the special flag.
+				// Note this method is valid only for 0x100-0x10ffff unicode value range.
+				ch | 0x01000000;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/com/glavsoft/utils/Strings.java	Thu Sep 11 07:30:03 2014 +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.utils;
+
+public class Strings {
+	public static String toString(byte[] byteArray) {
+		StringBuilder sb = new StringBuilder("[");
+		boolean notFirst = false;
+		for (byte b : byteArray) {
+			if (notFirst) {
+				sb.append(", ");
+			} else {
+				notFirst = true;
+			}
+			sb.append(b);
+		}
+		return sb.append("]").toString();
+	}
+
+	public static boolean isTrimmedEmpty(String s) {
+		return null == s || (s.trim().length() == 0);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/AbstractConnectionWorkerFactory.java	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/Viewer.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,264 @@
+// 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.viewer.cli.Parser;
+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.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.logging.*;
+
+@SuppressWarnings("serial")
+public class Viewer extends JApplet implements Runnable, WindowListener {
+
+	private Logger logger;
+    private int paramsMask;
+    private boolean allowAppletInteractiveConnections;
+
+    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;
+
+    public static void main(String[] args) {
+		Parser parser = new Parser();
+		ParametersHandler.completeParserOptions(parser);
+
+		parser.parse(args);
+		if (parser.isSet(ParametersHandler.ARG_HELP)) {
+			printUsage(parser.optionsUsage());
+			System.exit(0);
+		}
+		Viewer viewer = new Viewer(parser);
+		SwingUtilities.invokeLater(viewer);
+	}
+
+    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" +
+				"    or\n java -jar (progfilename) -help\n    to view this help\n\n" +
+				"Where Options are:\n" + additional +
+				"\nOptions format: -optionName=optionValue. Ex. -host=localhost -port=5900 -viewonly=yes\n" +
+				"Both option name and option value are case insensitive.");
+	}
+
+	public Viewer() {
+        logger = Logger.getLogger(getClass().getName());
+		connectionParams = new ConnectionParams();
+		settings = ProtocolSettings.getDefaultSettings();
+		uiSettings = new UiSettings();
+	}
+
+	private Viewer(Parser parser) {
+		this();
+        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 windowClosing(WindowEvent e) {
+		if (e != null && e.getComponent() != null) {
+            final Window w = e.getWindow();
+            if (w != null) {
+                w.setVisible(false);
+                w.dispose();
+            }
+		}
+		closeApp();
+	}
+
+	/**
+	 * Closes App(lication) or stops App(let).
+	 */
+    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);
+		}
+	}
+
+	@Override
+	public void paint(Graphics g) {
+		if ( ! isAppletStopped) {
+			super.paint(g);
+		} else {
+			getContentPane().removeAll();
+			g.clearRect(0, 0, getWidth(), getHeight());
+			g.drawString("Disconnected", 10, 20);
+		}
+	}
+
+	@Override
+	public void destroy() {
+		closeApp();
+		super.destroy();
+	}
+
+	@Override
+	public void init() {
+		paramsMask = ParametersHandler.completeSettingsFromApplet(this, connectionParams, settings, uiSettings);
+		isSeparateFrame = ParametersHandler.isSeparateFrame;
+		passwordFromParams = getParameter(ParametersHandler.ARG_PASSWORD);
+		isApplet = true;
+        allowAppletInteractiveConnections = ParametersHandler.allowAppletInteractiveConnections;
+		repaint();
+
+        try {
+            SwingUtilities.invokeAndWait(this);
+        } catch (Exception e) {
+            logger.severe(e.getMessage());
+        }
+    }
+
+	@Override
+	public void start() {
+		super.start();
+	}
+
+    private boolean checkJsch() {
+        try {
+            Class.forName("com.jcraft.jsch.JSch");
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
+    @Override
+	public void run() {
+
+        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);
+                }
+            });
+        }
+
+        SwingViewerWindowFactory viewerWindowFactory = new SwingViewerWindowFactory(isSeparateFrame, isApplet, this);
+
+        connectionPresenter.setConnectionWorkerFactory(
+                new SwingConnectionWorkerFactory(connectionView.getFrame(), passwordFromParams, connectionPresenter, viewerWindowFactory));
+
+        connectionPresenter.startConnection(settings, uiSettings, paramsMask);
+	}
+
+	@Override
+	public void windowOpened(WindowEvent e) { /* nop */ }
+	@Override
+	public void windowClosed(WindowEvent e) { /* nop */ }
+	@Override
+	public void windowIconified(WindowEvent e) { /* nop */ }
+	@Override
+	public void windowDeiconified(WindowEvent e) { /* nop */ }
+	@Override
+	public void windowActivated(WindowEvent e) { /* nop */ }
+	@Override
+	public void windowDeactivated(WindowEvent e) { /* nop */ }
+
+	public static String ver() {
+		final InputStream mfStream = Viewer.class.getClassLoader().getResourceAsStream(
+				"META-INF/MANIFEST.MF");
+		if (null == mfStream) {
+			System.out.println("No Manifest file found.");
+			return "-1";
+		}
+		try {
+			Manifest mf = new Manifest();
+			mf.read(mfStream);
+			Attributes atts = mf.getMainAttributes();
+			return atts.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+		} catch (IOException e) {
+			return "-2";
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/cli/Parser.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,117 @@
+// 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.cli;
+
+import com.glavsoft.utils.Strings;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Command line interface parameters parser
+ */
+public class Parser {
+	private final Map<String, Option> options = new LinkedHashMap<String, Option>();
+	private final List<String> plainOptions = new ArrayList<String>();
+	private boolean isSetPlainOptions = false;
+
+	public void addOption(String opName, String defaultValue, String desc) {
+		Option op = new Option(opName, defaultValue, desc);
+		options.put(opName.toLowerCase(), op);
+	}
+
+	public void parse(String [] args) {
+		for (String p : args) {
+			if (p.startsWith("-")) {
+				int skipMinuses = p.startsWith("--") ? 2 : 1;
+				String[] params = p.split("=", 2);
+				Option op = options.get(params[0].toLowerCase().substring(skipMinuses));
+				if (op != null) {
+					op.isSet = true;
+					if (params.length > 1 && ! Strings.isTrimmedEmpty(params[1])) {
+						op.value = params[1];
+					}
+				}
+			} else if ( ! p.startsWith("-")) {
+				isSetPlainOptions = true;
+				plainOptions.add(p);
+			}
+		}
+	}
+
+	public String getValueFor(String param) {
+		Option op = options.get(param.toLowerCase());
+		return op != null ? op.value : null;
+	}
+
+	public boolean isSet(String param) {
+		Option op = options.get(param.toLowerCase());
+		return op != null && op.isSet;
+	}
+
+	public boolean isSetPlainOptions() {
+		return isSetPlainOptions;
+	}
+
+	public String getPlainOptionAt(int index) {
+		return plainOptions.get(index);
+	}
+
+	public int getPlainOptionsNumber() {
+		return plainOptions.size();
+	}
+
+	/**
+	 * Command line interface option
+	 */
+	private static class Option {
+		protected String opName, defaultValue, desc, value;
+		protected boolean isSet = false;
+		public Option(String opName, String defaultValue, String desc) {
+			this.opName = opName;
+			this.defaultValue = defaultValue;
+			this.desc = desc;
+			this.value = defaultValue;
+		}
+	}
+
+	public String optionsUsage() {
+		StringBuilder sb = new StringBuilder();
+		int maxNameLength = 0;
+		for (Option op : options.values()) {
+			maxNameLength = Math.max(maxNameLength, op.opName.length());
+		}
+		for (Option op : options.values()) {
+			sb.append(" -").append(op.opName);
+			for (int i=0; i<maxNameLength - op.opName.length(); ++i) {
+				sb.append(' ');
+			}
+			sb.append(" : ").append(op.desc).append('\n');
+		}
+		return sb.toString();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/mvp/Model.java	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ClipboardControllerImpl.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,154 @@
+// 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.ClipboardController;
+import com.glavsoft.rfb.client.ClientCutTextMessage;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.utils.Strings;
+
+import java.awt.*;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.AccessControlException;
+
+public class ClipboardControllerImpl implements ClipboardController, Runnable {
+	private static final String STANDARD_CHARSET = "ISO-8859-1"; // aka Latin-1
+	private static final long CLIPBOARD_UPDATE_CHECK_INTERVAL_MILS = 1000L;
+	private Clipboard clipboard;
+	private String clipboardText = null;
+	private volatile boolean isRunning;
+	private boolean isEnabled;
+	private final ProtocolContext context;
+	private Charset charset;
+
+	public ClipboardControllerImpl(ProtocolContext context, String charsetName) {
+		this.context = context;
+		try {
+			clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+			updateSavedClipboardContent(); // prevent onstart clipboard content sending
+		} catch (AccessControlException e) { /*nop*/ }
+		
+		if (Strings.isTrimmedEmpty(charsetName)) {
+			charset = Charset.defaultCharset();
+		} else if ("standard".equalsIgnoreCase(charsetName)) {
+			charset = Charset.forName(STANDARD_CHARSET);
+		} else {
+			charset = Charset.isSupported(charsetName) ? Charset.forName(charsetName) : Charset.defaultCharset();
+		}
+		// not supported UTF-charsets as they are multibytes.
+		// add others multibytes charsets on need
+		if (charset.name().startsWith("UTF")) {
+			charset = Charset.forName(STANDARD_CHARSET);
+		}
+	}
+
+	@Override
+	public void updateSystemClipboard(byte[] bytes) {
+		if (clipboard != null) {
+			StringSelection stringSelection = new StringSelection(new String(bytes, charset));
+			if (isEnabled) {
+				clipboard.setContents(stringSelection, null);
+			}
+		}
+	}
+
+	/**
+	 *	Callback for clipboard changes listeners
+	 *  Retrieves text content from system clipboard which then available
+	 *  through getClipboardText().
+	 */
+	private void updateSavedClipboardContent() {
+		if (clipboard != null && clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
+			try {
+				clipboardText = (String)clipboard.getData(DataFlavor.stringFlavor);
+			} catch (UnsupportedFlavorException e) {
+				// ignore
+			} catch (IOException e) {
+				// ignore
+			}
+		} else {
+			clipboardText = null;
+		}
+	}
+
+	@Override
+	public String getClipboardText() {
+		return clipboardText;
+	}
+
+	/**
+	 * Get text clipboard contents when needed send to remote, or null vise versa
+	 *
+	 * @return clipboard string contents if it is changed from last method call
+	 * or null when clipboard contains non text object or clipboard contents didn't changed
+	 */
+	@Override
+	public String getRenewedClipboardText() {
+		String old = clipboardText;
+		updateSavedClipboardContent();
+		if (clipboardText != null && ! clipboardText.equals(old))
+			return clipboardText;
+		return null;
+	}
+
+	@Override
+	public void setEnabled(boolean enable) {
+		if (! enable) {
+			isRunning = false;
+		}
+		if (enable && ! isEnabled) {
+			new Thread(this).start();
+		}
+		isEnabled = enable;
+	}
+
+	@Override
+	public void run() {
+		isRunning = true;
+		while (isRunning) {
+			String clipboardText = getRenewedClipboardText();
+			if (clipboardText != null) {
+				context.sendMessage(new ClientCutTextMessage(clipboardText.getBytes(charset)));
+			}
+			try {
+				Thread.sleep(CLIPBOARD_UPDATE_CHECK_INTERVAL_MILS);
+			} catch (InterruptedException e) { continue; }
+		}
+	}
+
+	@Override
+	public void settingsChanged(SettingsChangedEvent e) {
+		ProtocolSettings settings = (ProtocolSettings) e.getSource();
+		setEnabled(settings.isAllowClipboardTransfer());
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ConnectionParams.java	Thu Sep 11 07:30:03 2014 +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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/KeyEventListener.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,209 @@
+// 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.rfb.client.KeyEventMessage;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+import static com.glavsoft.utils.Keymap.*;
+
+public class KeyEventListener implements KeyListener {
+
+
+	private ModifierButtonEventListener modifierButtonListener;
+	private boolean convertToAscii;
+	private final ProtocolContext context;
+	private KeyboardConvertor convertor;
+
+	public KeyEventListener(ProtocolContext context) {
+		this.context = context;
+		this.convertToAscii = false;
+	}
+
+	private void processKeyEvent(KeyEvent e) {
+		if (processModifierKeys(e)) return;
+		if (processSpecialKeys(e)) return;
+		if (processActionKey(e)) return;
+
+		int keyChar = e.getKeyChar();
+		final int location = e.getKeyLocation();
+		if (0xffff == keyChar) { keyChar = convertToAscii? convertor.convert(keyChar, e) : 0; }
+		if (keyChar < 0x20) {
+			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;
+				case KeyEvent.VK_TAB: keyChar = K_TAB; break;
+				case KeyEvent.VK_ESCAPE: keyChar = K_ESCAPE; break;
+				case KeyEvent.VK_ENTER:
+					keyChar = KeyEvent.KEY_LOCATION_NUMPAD == location ? K_KP_ENTER : K_ENTER;
+					break;
+				}
+			}
+		} else if (KeyEvent.VK_DELETE == keyChar) {
+			keyChar = K_DELETE;
+		} else if (convertToAscii) {
+			keyChar = convertor.convert(keyChar, e);
+		} else {
+			keyChar = unicode2keysym(keyChar);
+		}
+
+		sendKeyEvent(keyChar, e);
+	}
+
+
+	/**
+	 * Process AltGraph, num pad keys...
+	 */
+	private boolean processSpecialKeys(KeyEvent e) {
+		int keyCode = e.getKeyCode();
+		if (KeyEvent.VK_ALT_GRAPH == keyCode) {
+			sendKeyEvent(K_CTRL_LEFT, e);
+			sendKeyEvent(K_ALT_LEFT, e);
+			return true;
+		}
+		switch (keyCode) {
+		case KeyEvent.VK_NUMPAD0: keyCode = K_KP_0;break;
+		case KeyEvent.VK_NUMPAD1: keyCode = K_KP_1;break;
+		case KeyEvent.VK_NUMPAD2: keyCode = K_KP_2;break;
+		case KeyEvent.VK_NUMPAD3: keyCode = K_KP_3;break;
+		case KeyEvent.VK_NUMPAD4: keyCode = K_KP_4;break;
+		case KeyEvent.VK_NUMPAD5: keyCode = K_KP_5;break;
+		case KeyEvent.VK_NUMPAD6: keyCode = K_KP_6;break;
+		case KeyEvent.VK_NUMPAD7: keyCode = K_KP_7;break;
+		case KeyEvent.VK_NUMPAD8: keyCode = K_KP_8;break;
+		case KeyEvent.VK_NUMPAD9: keyCode = K_KP_9;break;
+
+		case KeyEvent.VK_MULTIPLY: keyCode = K_KP_MULTIPLY;break;
+		case KeyEvent.VK_ADD: keyCode = K_KP_ADD;break;
+		case KeyEvent.VK_SEPARATOR: keyCode = K_KP_SEPARATOR;break;
+		case KeyEvent.VK_SUBTRACT: keyCode = K_KP_SUBTRACT;break;
+		case KeyEvent.VK_DECIMAL: keyCode = K_KP_DECIMAL;break;
+		case KeyEvent.VK_DIVIDE: keyCode = K_KP_DIVIDE;break;
+
+		default: return false;
+		}
+		sendKeyEvent(keyCode, e);
+		return true;
+	}
+
+	private boolean processActionKey(KeyEvent e) {
+		int keyCode = e.getKeyCode();
+		final int location = e.getKeyLocation();
+		if (e.isActionKey()) {
+			switch (keyCode) {
+			case KeyEvent.VK_HOME: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_HOME: K_HOME; break;
+			case KeyEvent.VK_LEFT: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_LEFT: K_LEFT; break;
+			case KeyEvent.VK_UP: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_UP: K_UP; break;
+			case KeyEvent.VK_RIGHT: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_RIGHT: K_RIGHT; break;
+			case KeyEvent.VK_DOWN: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_DOWN: K_DOWN; break;
+			case KeyEvent.VK_PAGE_UP: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_PAGE_UP: K_PAGE_UP; break;
+			case KeyEvent.VK_PAGE_DOWN: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_PAGE_DOWN: K_PAGE_DOWN; break;
+			case KeyEvent.VK_END: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_END: K_END; break;
+			case KeyEvent.VK_INSERT: keyCode = KeyEvent.KEY_LOCATION_NUMPAD == location? K_KP_INSERT: K_INSERT; break;
+			case KeyEvent.VK_F1: keyCode = K_F1; break;
+			case KeyEvent.VK_F2: keyCode = K_F2; break;
+			case KeyEvent.VK_F3: keyCode = K_F3; break;
+			case KeyEvent.VK_F4: keyCode = K_F4; break;
+			case KeyEvent.VK_F5: keyCode = K_F5; break;
+			case KeyEvent.VK_F6: keyCode = K_F6; break;
+			case KeyEvent.VK_F7: keyCode = K_F7; break;
+			case KeyEvent.VK_F8: keyCode = K_F8; break;
+			case KeyEvent.VK_F9: keyCode = K_F9; break;
+			case KeyEvent.VK_F10: keyCode = K_F10; break;
+			case KeyEvent.VK_F11: keyCode = K_F11; break;
+			case KeyEvent.VK_F12: keyCode = K_F12; break;
+
+			case KeyEvent.VK_KP_LEFT: keyCode = K_KP_LEFT; break;
+			case KeyEvent.VK_KP_UP: keyCode = K_KP_UP; break;
+			case KeyEvent.VK_KP_RIGHT: keyCode = K_KP_RIGHT; break;
+			case KeyEvent.VK_KP_DOWN: keyCode = K_KP_DOWN; break;
+
+			default: return false; // ignore other 'action' keys
+			}
+			sendKeyEvent(keyCode, e);
+			return true;
+		}
+		return false;
+	}
+
+	private boolean processModifierKeys(KeyEvent e) {
+		int keyCode = e.getKeyCode();
+		switch (keyCode) {
+			case KeyEvent.VK_CONTROL: keyCode = K_CTRL_LEFT; break;
+			case KeyEvent.VK_SHIFT: keyCode = K_SHIFT_LEFT; break;
+			case KeyEvent.VK_ALT: keyCode = K_ALT_LEFT; break;
+			case KeyEvent.VK_META: keyCode = K_META_LEFT; break;
+			// follow two are 'action' keys in java terms but modifier keys actualy
+			case KeyEvent.VK_WINDOWS: keyCode = K_SUPER_LEFT; break;
+			case KeyEvent.VK_CONTEXT_MENU: keyCode = K_HYPER_LEFT; break;
+			default: return false;
+		}
+		if (modifierButtonListener != null) {
+			modifierButtonListener.fireEvent(e);
+		}
+		sendKeyEvent(keyCode +
+				(e.getKeyLocation() == KeyEvent.KEY_LOCATION_RIGHT ? 1 : 0), // "Right" Ctrl/Alt/Shift/Meta deffers frim "Left" ones by +1
+				e);
+		return true;
+	}
+
+	private void sendKeyEvent(int keyChar, KeyEvent e) {
+		context.sendMessage(new KeyEventMessage(keyChar, e.getID() == KeyEvent.KEY_PRESSED));
+	}
+
+	@Override
+	public void keyTyped(KeyEvent e) {
+		e.consume();
+	}
+
+	@Override
+	public void keyPressed(KeyEvent e) {
+		processKeyEvent(e);
+		e.consume();
+	}
+
+	@Override
+	public void keyReleased(KeyEvent e) {
+		processKeyEvent(e);
+		e.consume();
+	}
+
+	public void addModifierListener(ModifierButtonEventListener modifierButtonListener) {
+		this.modifierButtonListener = modifierButtonListener;
+	}
+
+	public void setConvertToAscii(boolean convertToAscii) {
+		this.convertToAscii = convertToAscii;
+		if (convertToAscii && null == convertor) {
+			convertor = new KeyboardConvertor();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/KeyboardConvertor.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,151 @@
+// 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 java.awt.Toolkit;
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class KeyboardConvertor {
+	private static final boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
+	private static final String PATTERN_STRING_FOR_SCANCODE = "scancode=(\\d+)";
+	private Pattern patternForScancode;
+	@SuppressWarnings("serial")
+	private static final Map<Integer, CodePair> keyMap = new HashMap<Integer, CodePair>() {{
+		put(192 /* Back Quote */, new CodePair('`' /*96*/, '~' /*126*/));
+		put(49 /* 1 */, new CodePair('1' /*49*/, '!' /*33*/));
+		put(50 /* 2 */, new CodePair('2' /*50*/, '@' /*64*/));
+		put(51 /* 3 */, new CodePair('3' /*51*/, '#' /*35*/));
+		put(52 /* 4 */, new CodePair('4' /*52*/, '$' /*36*/));
+		put(53 /* 5 */, new CodePair('5' /*53*/, '%' /*37*/));
+		put(54 /* 6 */, new CodePair('6' /*54*/, '^' /*94*/));
+		put(55 /* 7 */, new CodePair('7' /*55*/, '&' /*38*/));
+		put(56 /* 8 */, new CodePair('8' /*56*/, '*' /*42*/));
+		put(57 /* 9 */, new CodePair('9' /*57*/, '(' /*40*/));
+		put(48 /* 0 */, new CodePair('0' /*48*/, ')' /*41*/));
+		put(45 /* Minus */, new CodePair('-' /*45*/, '_' /*95*/));
+		put(61 /* Equals */, new CodePair('=' /*61*/, '+' /*43*/));
+		put(92 /* Back Slash */, new CodePair('\\' /*92*/, '|' /*124*/));
+
+		put(81 /* Q */, new CodePair('q' /*113*/, 'Q' /*81*/));
+		put(87 /* W */, new CodePair('w' /*119*/, 'W' /*87*/));
+		put(69 /* E */, new CodePair('e' /*101*/, 'E' /*69*/));
+		put(82 /* R */, new CodePair('r' /*114*/, 'R' /*82*/));
+		put(84 /* T */, new CodePair('t' /*116*/, 'T' /*84*/));
+		put(89 /* Y */, new CodePair('y' /*121*/, 'Y' /*89*/));
+		put(85 /* U */, new CodePair('u' /*117*/, 'U' /*85*/));
+		put(73 /* I */, new CodePair('i' /*105*/, 'I' /*73*/));
+		put(79 /* O */, new CodePair('o' /*111*/, 'O' /*79*/));
+		put(80 /* P */, new CodePair('p' /*112*/, 'P' /*80*/));
+		put(91 /* Open Bracket */, new CodePair('[' /*91*/, '{' /*123*/));
+		put(93 /* Close Bracket */, new CodePair(']' /*93*/, '}' /*125*/));
+
+		put(65 /* A */, new CodePair('a' /*97*/, 'A' /*65*/));
+		put(83 /* S */, new CodePair('s' /*115*/, 'S' /*83*/));
+		put(68 /* D */, new CodePair('d' /*100*/, 'D' /*68*/));
+		put(70 /* F */, new CodePair('f' /*102*/, 'F' /*70*/));
+		put(71 /* G */, new CodePair('g' /*103*/, 'G' /*71*/));
+		put(72 /* H */, new CodePair('h' /*104*/, 'H' /*72*/));
+		put(74 /* J */, new CodePair('j' /*106*/, 'J' /*74*/));
+		put(75 /* K */, new CodePair('k' /*107*/, 'K' /*75*/));
+		put(76 /* L */, new CodePair('l' /*108*/, 'L' /*76*/));
+		put(59 /* Semicolon */, new CodePair(';' /*59*/, ':' /*58*/));
+		put(222 /* Quote */, new CodePair('\'' /*39*/, '"' /*34*/));
+
+		put(90 /* Z */, new CodePair('z' /*122*/, 'Z' /*90*/));
+		put(88 /* X */, new CodePair('x' /*120*/, 'X' /*88*/));
+		put(67 /* C */, new CodePair('c' /*99*/, 'C' /*67*/));
+		put(86 /* V */, new CodePair('v' /*118*/, 'V' /*86*/));
+		put(66 /* B */, new CodePair('b' /*98*/, 'B' /*66*/));
+		put(78 /* N */, new CodePair('n' /*110*/, 'N' /*78*/));
+		put(77 /* M */, new CodePair('m' /*109*/, 'M' /*77*/));
+		put(44 /* Comma */, new CodePair(',' /*44*/, '<' /*60*/));
+		put(46 /* Period */, new CodePair('.' /*46*/, '>' /*62*/));
+		put(47 /* Slash */, new CodePair('/' /*47*/, '?' /*63*/));
+
+//		put(60 /* Less */, new CodePair('<' /*60*/, '>')); // 105-th key on 105-keys keyboard (less/greather/bar)
+		put(KeyEvent.VK_LESS /* Less */, new CodePair('<' /*60*/, '>')); // 105-th key on 105-keys keyboard (less/greather/bar)
+//		put(KeyEvent.VK_GREATER /* Greater */, new CodePair('<' /*60*/, '>')); // 105-th key on 105-keys keyboard (less/greather/bar)
+	}};
+
+	private static boolean canCheckCapsWithToolkit;
+
+	public KeyboardConvertor() {
+		try {
+			Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
+			canCheckCapsWithToolkit = true;
+		} catch (Exception e) {
+			canCheckCapsWithToolkit = false;
+		}
+		if (isWindows) {
+			patternForScancode = Pattern.compile(PATTERN_STRING_FOR_SCANCODE);
+		}
+	}
+
+	public int convert(int keyChar, KeyEvent ev) {
+		int keyCode = ev.getKeyCode();
+		boolean isShiftDown = ev.isShiftDown();
+		CodePair codePair = keyMap.get(keyCode);
+		if (null == codePair)
+			return keyChar;
+		if (isWindows) {
+			final Matcher matcher = patternForScancode.matcher(ev.paramString());
+			if (matcher.matches()) {
+				try {
+				int scancode = Integer.parseInt(matcher.group(1));
+				if (90 == keyCode && 21 == scancode) { // deutsch z->y
+					codePair = keyMap.get(89); // y
+				} else if (89 == keyCode && 44 == scancode) { // deutsch y->z
+					codePair = keyMap.get(90); // z
+				}
+				} catch (NumberFormatException e) { /*nop*/ };
+			}
+		}
+		boolean isCapsLock = false;
+		if (Character.isLetter(codePair.code)) {
+			if (canCheckCapsWithToolkit) {
+				try {
+					isCapsLock =
+						Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK);
+				} catch (Exception ex) { /* nop */ }
+			}
+		}
+		return isShiftDown && ! isCapsLock || ! isShiftDown && isCapsLock ?
+				codePair.codeShifted :
+				codePair.code;
+	}
+
+	private static class CodePair {
+		public int code, codeShifted;
+		public CodePair(int code, int codeShifted) {
+			this.code = code;
+			this.codeShifted = codeShifted;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/LocalMouseCursorShape.java	Thu Sep 11 07:30:03 2014 +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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ModifierButtonEventListener.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,44 @@
+// 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 java.awt.event.KeyEvent;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JToggleButton;
+
+public class ModifierButtonEventListener {
+	Map<Integer, JToggleButton> buttons = new HashMap<Integer, JToggleButton>();
+	public void addButton(int keyCode, JToggleButton button) {
+		buttons.put(keyCode, button);
+	}
+	public void fireEvent(KeyEvent e) {
+		int code = e.getKeyCode();
+		if (buttons.containsKey(code)) {
+			buttons.get(code).setSelected(e.getID() == KeyEvent.KEY_PRESSED);
+		}
+	}
+}
\ 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/MouseEventListener.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,123 @@
+// 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.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;
+
+public class MouseEventListener extends MouseInputAdapter
+implements MouseWheelListener {
+	private static final byte BUTTON_LEFT = 1;
+	private static final byte BUTTON_MIDDLE = 1 << 1;
+	private static final byte BUTTON_RIGHT = 1 << 2;
+	private static final byte WHEEL_UP = 1 << 3;
+	private static final byte WHEEL_DOWN = 1 << 4;
+
+	private final IRepaintController repaintController;
+	private final ProtocolContext context;
+	private volatile double scaleFactor;
+
+	public MouseEventListener(IRepaintController repaintController, ProtocolContext context,
+			double scaleFactor) {
+		this.repaintController = repaintController;
+		this.context = context;
+		this.scaleFactor = scaleFactor;
+	}
+
+	public void processMouseEvent(MouseEvent mouseEvent,
+			MouseWheelEvent mouseWheelEvent, boolean moved) {
+		byte buttonMask = 0;
+		if (null == mouseEvent && mouseWheelEvent != null) {
+			mouseEvent = mouseWheelEvent;
+		}
+		assert mouseEvent != null;
+		short x = (short) (mouseEvent.getX() / scaleFactor);
+		short y = (short) (mouseEvent.getY() / scaleFactor);
+		if (moved) {
+			repaintController.updateCursorPosition(x, y);
+		}
+
+		int modifiersEx = mouseEvent.getModifiersEx();
+		// left
+		buttonMask |= (modifiersEx & InputEvent.BUTTON1_DOWN_MASK) != 0 ?
+				BUTTON_LEFT : 0;
+		// middle
+		buttonMask |= (modifiersEx & InputEvent.BUTTON2_DOWN_MASK) != 0 ?
+				BUTTON_MIDDLE : 0;
+		// right
+		buttonMask |= (modifiersEx & InputEvent.BUTTON3_DOWN_MASK) != 0 ?
+				BUTTON_RIGHT : 0;
+
+		// wheel
+		if (mouseWheelEvent != null) {
+			int notches = mouseWheelEvent.getWheelRotation();
+			byte wheelMask = notches < 0 ? WHEEL_UP : WHEEL_DOWN;
+			// handle more then 1 notches
+			notches = Math.abs(notches);
+			for (int i=1; i<notches; ++i) {
+				context.sendMessage(new PointerEventMessage((byte) (buttonMask | wheelMask), x, y));
+				context.sendMessage(new PointerEventMessage(buttonMask, x, y));
+			}
+			context.sendMessage(new PointerEventMessage((byte) (buttonMask | wheelMask), x, y));
+		}
+		context.sendMessage(new PointerEventMessage(buttonMask, x, y));
+	}
+
+	@Override
+	public void mousePressed(MouseEvent mouseEvent) {
+		processMouseEvent(mouseEvent, null, false);
+	}
+
+	@Override
+	public void mouseReleased(MouseEvent mouseEvent) {
+		processMouseEvent(mouseEvent, null, false);
+	}
+
+	@Override
+	public void mouseDragged(MouseEvent mouseEvent) {
+		processMouseEvent(mouseEvent, null, true);
+	}
+
+	@Override
+	public void mouseMoved(MouseEvent mouseEvent) {
+		processMouseEvent(mouseEvent, null, true);
+	}
+
+	@Override
+	public void mouseWheelMoved(MouseWheelEvent emouseWheelEvent) {
+		processMouseEvent(null, emouseWheelEvent, false);
+	}
+
+	public void setScaleFactor(double scaleFactor) {
+		this.scaleFactor = scaleFactor;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ParametersHandler.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,318 @@
+// 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.rfb.encoding.EncodingType;
+import com.glavsoft.rfb.protocol.LocalPointer;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.utils.Strings;
+import com.glavsoft.viewer.UiSettings;
+import com.glavsoft.viewer.cli.Parser;
+
+import javax.swing.*;
+
+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";
+	public static final String ARG_ENCODING = "Encoding";
+	public static final String ARG_SHARE_DESKTOP = "ShareDesktop";
+	public static final String ARG_ALLOW_COPY_RECT = "AllowCopyRect";
+	public static final String ARG_VIEW_ONLY = "ViewOnly";
+	public static final String ARG_SHOW_CONTROLS = "ShowControls";
+	public static final String ARG_OPEN_NEW_WINDOW = "OpenNewWindow";
+	public static final String ARG_PASSWORD = "password";
+	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 boolean isSeparateFrame;
+    public static boolean allowAppletInteractiveConnections;
+
+    public static void completeParserOptions(Parser parser) {
+		parser.addOption(ARG_HELP, null, "Print this help.");
+		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\".");
+		parser.addOption(ARG_VIEW_ONLY, null, "When set to \"Yes\", then all keyboard and mouse " +
+				"events in the desktop window will be silently ignored and will not be passed " +
+				"to the remote side. Default: \"No\".");
+		parser.addOption(ARG_ALLOW_CLIPBOARD_TRANSFER, null, "When set to \"Yes\", transfer of clipboard contents is allowed. " +
+				"Default: \"Yes\".");
+		parser.addOption(ARG_REMOTE_CHARSET, null, "Charset encoding is used on remote system. Use this option to specify character encoding will be used for encoding clipboard text content to. Default value: local system default character encoding. Set the value to 'standard' for using 'Latin-1' charset which is only specified by rfb standard for clipboard transfers.");
+		parser.addOption(ARG_SHARE_DESKTOP, null, "Share the connection with other clients " +
+				"on the same VNC server. The exact behaviour in each case depends on the server " +
+				"configuration. Default: \"Yes\".");
+		parser.addOption(ARG_ALLOW_COPY_RECT, null, "The \"CopyRect\" encoding saves bandwidth " +
+				"and drawing time when parts of the remote screen are moving around. " +
+				"Most likely, you don't want to change this setting. Default: \"Yes\".");
+		parser.addOption(ARG_ENCODING, null, "The preferred encoding. Possible values: \"Tight\", " +
+				"\"Hextile\", \"ZRLE\", and \"Raw\". Default: \"Tight\".");
+		parser.addOption(ARG_COMPRESSION_LEVEL, null, "Use specified compression level for " +
+				"\"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.");
+        //noinspection ConstantConditions
+        parser.addOption(ARG_JPEG_IMAGE_QUALITY, null, "Use the specified image quality level " +
+				"in \"Tight\" encoding. Values: 1-9, Lossless. Default value: " +
+				(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). " +
+		"Default: \"On\".");
+		parser.addOption(ARG_CONVERT_TO_ASCII, null, "Whether to convert keyboard input to ASCII ignoring locale. Possible values: yes/true, no/false). " +
+		"Default: \"No\".");
+		parser.addOption(ARG_COLOR_DEPTH, null, "Bits per pixel color format. Possible values: 3 (for 8 colors), 6 (64 colors), 8 (256 colors), 16 (65 536 colors), 24 (16 777 216 colors), 32 (same as 24).");
+		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 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);
+		}
+		if (parser.isSetPlainOptions()) {
+			splitConnectionParams(connectionParams, parser.getPlainOptionAt(0));
+			if (parser.getPlainOptionsNumber() > 1) {
+                try {
+                    connectionParams.parseRfbPortNumber(parser.getPlainOptionAt(1));
+                } catch (WrongParameterException e) {
+                    //nop
+                }
+            }
+		}
+        return mask;
+	}
+
+
+	/**
+	 * Split host string into hostName + port number and set ConnectionParans.
+	 * a.b.c.d:5000 -> hostName == a.b.c.d, portNumber == 5000
+	 * a.b.c.d::5000 -> hostName == a.b.c.d, portNumber == 5000
+	 */
+	public static void splitConnectionParams(final ConnectionParams connectionParams, String host) {
+		int indexOfColon = host.indexOf(':');
+		if (indexOfColon > 0) {
+			String[] splitted = host.split(":");
+			connectionParams.hostName = splitted[0];
+			if (splitted.length > 1) {
+                try {
+                    connectionParams.parseRfbPortNumber(splitted[splitted.length - 1]);
+                } catch (WrongParameterException e) {
+                    //nop
+                }
+            }
+		} else {
+			connectionParams.hostName = host;
+		}
+	}
+
+	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.parseRfbPortNumber(portNumber);
+        } catch (WrongParameterException e) {
+            //nop
+        }
+
+        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));
+        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 {
+			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)) {
+				rfbSettings.setJpegQuality( - Math.abs(rfbSettings.getJpegQuality()));
+			}
+		}
+		try {
+			int colorDepth = Integer.parseInt(colorDepthParam);
+			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) ||
+			"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 int completeSettingsFromApplet(final JApplet applet,
+			ConnectionParams connectionParams, ProtocolSettings rfbSettings, UiSettings uiSettings) {
+        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;
+	}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/RendererImpl.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,109 @@
+// 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.drawing.Renderer;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
+import com.glavsoft.transport.Reader;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class RendererImpl extends Renderer implements ImageObserver {
+    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;
+		init(reader, width, height, pixelFormat);
+		ColorModel colorModel = new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
+		SampleModel sampleModel = colorModel.createCompatibleSampleModel(width,
+				height);
+
+		DataBuffer dataBuffer = new DataBufferInt(pixels, width * height);
+		WritableRaster raster = Raster.createWritableRaster(sampleModel,
+				dataBuffer, null);
+		offscreanImage = new BufferedImage(colorModel, raster, false, null);
+		cursor = new SoftCursorImpl(0, 0, 0, 0);
+	}
+
+	/**
+	 * Draw jpeg image data
+	 *
+	 * @param bytes jpeg image data array
+	 * @param offset start offset at data array
+	 * @param jpegBufferLength jpeg image data array length
+	 * @param rect image location and dimensions
+	 */
+	@Override
+	public void drawJpegImage(byte[] bytes, int offset, int jpegBufferLength,
+			FramebufferUpdateRectangle rect) {
+		Image jpegImage = Toolkit.getDefaultToolkit().createImage(bytes,
+				offset, jpegBufferLength);
+		Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
+		try {
+			barrier.await(3, TimeUnit.SECONDS);
+		} catch (InterruptedException e) {
+			// nop
+		} catch (BrokenBarrierException e) {
+			// nop
+		} catch (TimeoutException e) {
+			// nop
+		}
+		Graphics graphics = offscreanImage.getGraphics();
+		graphics.drawImage(jpegImage, rect.x, rect.y, rect.width, rect.height, this);
+	}
+
+	@Override
+	public boolean imageUpdate(Image img, int infoflags, int x, int y,
+			int width, int height) {
+		boolean isReady = (infoflags & (ALLBITS | ABORT)) != 0;
+		if (isReady) {
+			try {
+				barrier.await();
+			} catch (InterruptedException e) {
+				// nop
+			} catch (BrokenBarrierException e) {
+				// nop
+			}
+		}
+		return ! isReady;
+	}
+
+	/* Swing specific interface */
+	public Image getOffscreenImage() {
+		return offscreanImage;
+	}
+
+	public SoftCursorImpl getCursor() {
+		return (SoftCursorImpl) cursor;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/SoftCursorImpl.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,51 @@
+// 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 java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.image.MemoryImageSource;
+
+import com.glavsoft.drawing.SoftCursor;
+
+public class SoftCursorImpl extends SoftCursor {
+	private Image cursorImage;
+
+	public SoftCursorImpl(int hotX, int hotY, int width, int height) {
+		super(hotX, hotY, width, height);
+	}
+
+	public Image getImage() {
+		return cursorImage;
+	}
+
+	@Override
+	protected void createNewCursorImage(int[] cursorPixels, int hotX, int hotY, int width,	int height) {
+		cursorImage = Toolkit.getDefaultToolkit().createImage(
+				new MemoryImageSource(width, height, cursorPixels, 0, width));
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/Surface.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,252 @@
+// 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.drawing.Renderer;
+import com.glavsoft.rfb.IChangeSettingsListener;
+import com.glavsoft.rfb.IRepaintController;
+import com.glavsoft.rfb.encoding.PixelFormat;
+import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
+import com.glavsoft.rfb.protocol.ProtocolContext;
+import com.glavsoft.rfb.protocol.ProtocolSettings;
+import com.glavsoft.transport.Reader;
+import com.glavsoft.viewer.UiSettings;
+
+import javax.swing.*;
+import java.awt.*;
+
+@SuppressWarnings("serial")
+public class Surface extends JPanel implements IRepaintController, IChangeSettingsListener {
+
+	private int width;
+	private int height;
+	private SoftCursorImpl cursor;
+	private RendererImpl renderer;
+	private MouseEventListener mouseEventListener;
+	private KeyEventListener keyEventListener;
+	private boolean showCursor;
+	private ModifierButtonEventListener modifierButtonListener;
+	private boolean isUserInputEnabled = false;
+	private final ProtocolContext context;
+    private SwingViewerWindow viewerWindow;
+    private double scaleFactor;
+	public Dimension oldSize;
+
+	@Override
+	public boolean isDoubleBuffered() {
+		// TODO returning false in some reason may speed ups drawing, but may
+		// not. Needed in challenging.
+		return false;
+	}
+
+	public Surface(ProtocolContext context, double scaleFactor, LocalMouseCursorShape mouseCursorShape) {
+		this.context = context;
+		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);
+	}
+
+    // 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) {
+			if (null == mouseEventListener) {
+				mouseEventListener = new MouseEventListener(this, context, scaleFactor);
+			}
+			addMouseListener(mouseEventListener);
+			addMouseMotionListener(mouseEventListener);
+			addMouseWheelListener(mouseEventListener);
+
+			setFocusTraversalKeysEnabled(false);
+			if (null == keyEventListener) {
+				keyEventListener = new KeyEventListener(context);
+				if (modifierButtonListener != null) {
+					keyEventListener.addModifierListener(modifierButtonListener);
+				}
+			}
+			keyEventListener.setConvertToAscii(convertToAscii);
+			addKeyListener(keyEventListener);
+			enableInputMethods(false);
+		} else {
+			removeMouseListener(mouseEventListener);
+			removeMouseMotionListener(mouseEventListener);
+			removeMouseWheelListener(mouseEventListener);
+			removeKeyListener(keyEventListener);
+		}
+	}
+
+	@Override
+	public Renderer createRenderer(Reader reader, int width, int height, PixelFormat pixelFormat) {
+		renderer = new RendererImpl(reader, width, height, pixelFormat);
+		synchronized (renderer.getLock()) {
+			cursor = renderer.getCursor();
+		}
+		init(renderer.getWidth(), renderer.getHeight());
+		updateFrameSize();
+		return renderer;
+	}
+
+	private void init(int width, int height) {
+		this.width = width;
+		this.height = height;
+		setSize(getPreferredSize());
+	}
+
+	private void updateFrameSize() {
+		setSize(getPreferredSize());
+		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.getLock()) {
+			Image offscreenImage = renderer.getOffscreenImage();
+			if (offscreenImage != null) {
+				g.drawImage(offscreenImage, 0, 0, null);
+			}
+		}
+		synchronized (cursor.getLock()) {
+			Image cursorImage = cursor.getImage();
+			if (showCursor && cursorImage != null &&
+					(scaleFactor != 1 ||
+							g.getClipBounds().intersects(cursor.rX, cursor.rY, cursor.width, cursor.height))) {
+				g.drawImage(cursorImage, cursor.rX, cursor.rY, null);
+			}
+		}
+	}
+
+	@Override
+	public Dimension getPreferredSize() {
+		return new Dimension((int)(this.width * scaleFactor), (int)(this.height * scaleFactor));
+	}
+
+	@Override
+	public Dimension getMinimumSize() {
+		return getPreferredSize();
+	}
+
+	@Override
+	public Dimension getMaximumSize() {
+		return getPreferredSize();
+	}
+
+	/**
+	 * Saves context and simply invokes native JPanel repaint method which
+	 * asyncroniously register repaint request using invokeLater to repaint be
+	 * runned in Swing event dispatcher thread. So may be called from other
+	 * threads.
+	 */
+	@Override
+	public void repaintBitmap(FramebufferUpdateRectangle rect) {
+		repaintBitmap(rect.x, rect.y, rect.width, rect.height);
+	}
+
+	@Override
+	public void repaintBitmap(int x, int y, int width, int height) {
+		repaint((int)(x * scaleFactor), (int)(y * scaleFactor),
+                (int)Math.ceil(width * scaleFactor), (int)Math.ceil(height * scaleFactor));
+	}
+
+	@Override
+	public void repaintCursor() {
+		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),
+					(int)Math.ceil(cursor.width * scaleFactor) + 1, (int)Math.ceil(cursor.height * scaleFactor) + 1);
+		}
+	}
+
+	@Override
+	public void updateCursorPosition(short x, short y) {
+		synchronized (cursor.getLock()) {
+			cursor.updatePosition(x, y);
+			repaintCursor();
+		}
+	}
+
+	private void showCursor(boolean show) {
+		synchronized (cursor.getLock()) {
+			showCursor = show;
+		}
+	}
+
+	public void addModifierListener(ModifierButtonEventListener modifierButtonListener) {
+		this.modifierButtonListener = modifierButtonListener;
+		if (keyEventListener != null) {
+			keyEventListener.addModifierListener(modifierButtonListener);
+		}
+	}
+
+	@Override
+	public void settingsChanged(SettingsChangedEvent e) {
+		if (ProtocolSettings.isRfbSettingsChangedFired(e)) {
+			ProtocolSettings settings = (ProtocolSettings) e.getSource();
+			setUserInputEnabled( ! settings.isViewOnly(), settings.isConvertToAscii());
+			showCursor(settings.isShowRemoteCursor());
+		} else if (UiSettings.isUiSettingsChangedFired(e)) {
+			UiSettings uiSettings = (UiSettings) e.getSource();
+			oldSize = getPreferredSize();
+			scaleFactor = uiSettings.getScaleFactor();
+            if (uiSettings.isChangedMouseCursorShape()) {
+                setLocalCursorShape(uiSettings.getMouseCursorShape());
+            }
+		}
+		mouseEventListener.setScaleFactor(scaleFactor);
+		updateFrameSize();
+	}
+
+    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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/Utils.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,136 @@
+// 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 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 java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utils for Swing GUI
+ */
+public class Utils {
+	private static List<Image> icons;
+
+	private static List<Image> getApplicationIcons() {
+		if (icons != null) {
+			return icons;
+		}
+		icons = new LinkedList<Image>();
+		URL resource = Utils.class.getResource("/com/glavsoft/viewer/images/tightvnc-logo-16x16.png");
+		Image image = resource != null ?
+				Toolkit.getDefaultToolkit().getImage(resource) :
+				null;
+		if (image != null) {
+			icons.add(image);
+		}
+		resource = Utils.class.getResource("/com/glavsoft/viewer/images/tightvnc-logo-32x32.png");
+		image = resource != null ?
+				Toolkit.getDefaultToolkit().getImage(resource) :
+				null;
+		if (image != null) {
+			icons.add(image);
+		}
+		return icons;
+	}
+
+	public static ImageIcon getButtonIcon(String name) {
+		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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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>";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/OptionsDialog.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,499 @@
+// 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.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.*;
+import java.awt.event.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Options dialog
+ */
+@SuppressWarnings("serial")
+public class OptionsDialog extends JDialog {
+	private JSlider jpegQuality;
+	private JSlider compressionLevel;
+	private JCheckBox viewOnlyCheckBox;
+	private ProtocolSettings settings;
+    private UiSettings uiSettings;
+    private JCheckBox sharedSession;
+
+	private RadioButtonSelectedState<LocalPointer> mouseCursorTrackSelected;
+	private Map<LocalPointer, JRadioButton> mouseCursorTrackMap;
+	private JCheckBox useCompressionLevel;
+	private JCheckBox useJpegQuality;
+	private JLabel jpegQualityPoorLabel;
+	private JLabel jpegQualityBestLabel;
+	private JLabel compressionLevelFastLabel;
+	private JLabel compressionLevelBestLabel;
+	private JCheckBox allowCopyRect;
+	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.DOCUMENT_MODAL);
+		final WindowAdapter onClose = new WindowAdapter() {
+			@Override
+			public void windowClosing(WindowEvent e) {
+				setVisible(false);
+			}
+		};
+		addWindowListener(onClose);
+
+		JPanel optionsPane = new JPanel(new GridLayout(0, 2));
+		add(optionsPane, BorderLayout.CENTER);
+
+		optionsPane.add(createLeftPane());
+		optionsPane.add(createRightPane());
+
+		addButtons(onClose);
+
+		pack();
+	}
+
+	public void initControlsFromSettings(ProtocolSettings settings, UiSettings uiSettings, boolean isOnConnect) {
+		this.settings = settings;
+        this.uiSettings = uiSettings;
+
+        viewOnlyCheckBox.setSelected(settings.isViewOnly());
+
+		int i = 0; boolean isNotSetEncoding = true;
+		while ( encodings.getItemAt(i) != null) {
+			EncodingType item = ((EncodingSelectItem)encodings.getItemAt(i)).type;
+			if (item.equals(settings.getPreferredEncoding())) {
+				encodings.setSelectedIndex(i);
+				isNotSetEncoding = false;
+				break;
+			}
+			++i;
+		}
+		if (isNotSetEncoding) {
+			encodings.setSelectedItem(0);
+		}
+
+		sharedSession.setSelected(settings.isShared());
+		sharedSession.setEnabled(isOnConnect);
+
+		mouseCursorTrackMap.get(settings.getMouseCursorTrack()).setSelected(true);
+		mouseCursorTrackSelected.setSelected(settings.getMouseCursorTrack());
+        mouseCursorShapeMap.get(uiSettings.getMouseCursorShape()).setSelected(true);
+        mouseCursorShapeSelected.setSelected(uiSettings.getMouseCursorShape());
+
+		int depth = settings.getColorDepth();
+		i = 0; boolean isNotSet = true;
+		while ( colorDepth.getItemAt(i) != null) {
+			int itemDepth = ((ColorDepthSelectItem)colorDepth.getItemAt(i)).depth;
+			if (itemDepth == depth) {
+				colorDepth.setSelectedIndex(i);
+				isNotSet = false;
+				break;
+			}
+			++i;
+		}
+		if (isNotSet) {
+			colorDepth.setSelectedItem(0);
+		}
+
+		useCompressionLevel.setSelected(settings.getCompressionLevel() > 0);
+		compressionLevel.setValue(Math.abs(settings.getCompressionLevel()));
+		setCompressionLevelPaneEnable();
+
+		useJpegQuality.setSelected(settings.getJpegQuality() > 0);
+		jpegQuality.setValue(Math.abs(settings.getJpegQuality()));
+		setJpegQualityPaneEnable();
+
+		allowCopyRect.setSelected(settings.isAllowCopyRect());
+		disableClipboardTransfer.setSelected( ! settings.isAllowClipboardTransfer());
+}
+
+	private void setSettingsFromControls() {
+		settings.setViewOnly(viewOnlyCheckBox.isSelected());
+		settings.setPreferredEncoding(((EncodingSelectItem)encodings.getSelectedItem()).type);
+
+		settings.setSharedFlag(sharedSession.isSelected());
+		settings.setMouseCursorTrack(mouseCursorTrackSelected.getSelected());
+        uiSettings.setMouseCursorShape(mouseCursorShapeSelected.getSelected());
+
+		settings.setColorDepth(((ColorDepthSelectItem) colorDepth.getSelectedItem()).depth);
+
+		settings.setCompressionLevel(useCompressionLevel.isSelected() ?
+				compressionLevel.getValue() :
+				- Math.abs(settings.getCompressionLevel()));
+		settings.setJpegQuality(useJpegQuality.isSelected() ?
+				jpegQuality.getValue() :
+				- Math.abs(settings.getJpegQuality()));
+		settings.setAllowCopyRect(allowCopyRect.isSelected());
+		settings.setAllowClipboardTransfer( ! disableClipboardTransfer.isSelected());
+		settings.fireListeners();
+	}
+
+	private Component createLeftPane() {
+		Box box = Box.createVerticalBox();
+		box.setAlignmentX(LEFT_ALIGNMENT);
+
+		box.add(createEncodingsPanel());
+
+		box.add(Box.createVerticalGlue());
+		return box;
+	}
+
+	private Component createRightPane() {
+		Box box = Box.createVerticalBox();
+		box.setAlignmentX(LEFT_ALIGNMENT);
+
+		box.add(createRestrictionsPanel());
+		box.add(createMouseCursorPanel());
+		box.add(createLocalShapePanel());
+
+		sharedSession = new JCheckBox("Request shared session");
+		box.add(new JPanel(new FlowLayout(FlowLayout.LEFT)).add(sharedSession));
+
+		box.add(Box.createVerticalGlue());
+		return box;
+	}
+
+	private JPanel createRestrictionsPanel() {
+		JPanel restrictionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+		restrictionsPanel.setBorder(
+				BorderFactory.createTitledBorder(
+						BorderFactory.createEtchedBorder(), "Restrictions"));
+
+		Box restrictionsBox = Box.createVerticalBox();
+		restrictionsBox.setAlignmentX(LEFT_ALIGNMENT);
+		restrictionsPanel.add(restrictionsBox);
+		viewOnlyCheckBox = new JCheckBox("View only (inputs ignored)");
+		viewOnlyCheckBox.setAlignmentX(LEFT_ALIGNMENT);
+		restrictionsBox.add(viewOnlyCheckBox);
+
+		disableClipboardTransfer = new JCheckBox("Disable clipboard transfer");
+		disableClipboardTransfer.setAlignmentX(LEFT_ALIGNMENT);
+		restrictionsBox.add(disableClipboardTransfer);
+
+		return restrictionsPanel;
+	}
+
+	private JPanel createEncodingsPanel() {
+		JPanel encodingsPanel = new JPanel();
+		encodingsPanel.setAlignmentX(LEFT_ALIGNMENT);
+		encodingsPanel.setLayout(new BoxLayout(encodingsPanel, BoxLayout.Y_AXIS));
+		encodingsPanel.setBorder(
+				BorderFactory.createTitledBorder(
+						BorderFactory.createEtchedBorder(), "Format and Encodings"));
+
+		JPanel encPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
+		encPane.setAlignmentX(LEFT_ALIGNMENT);
+		encPane.add(new JLabel("Preferred encoding: "));
+
+		encodings = new JComboBox();
+		encodings.addItem(new EncodingSelectItem(EncodingType.TIGHT));
+		encodings.addItem(new EncodingSelectItem(EncodingType.HEXTILE));
+
+//		encodings.addItem(new EncodingSelectItem(EncodingType.RRE));
+//		encodings.addItem(new EncodingSelectItem(EncodingType.ZLIB));
+
+		encodings.addItem(new EncodingSelectItem(EncodingType.ZRLE));
+		encodings.addItem(new EncodingSelectItem(EncodingType.RAW_ENCODING));
+		encPane.add(encodings);
+		encodingsPanel.add(encPane);
+
+		encodingsPanel.add(createColorDepthPanel());
+
+		addCompressionLevelPane(encodingsPanel);
+		addJpegQualityLevelPane(encodingsPanel);
+
+		allowCopyRect = new JCheckBox("Allow CopyRect encoding");
+		allowCopyRect.setAlignmentX(LEFT_ALIGNMENT);
+		encodingsPanel.add(allowCopyRect);
+
+		return encodingsPanel;
+	}
+
+	private static class EncodingSelectItem {
+		final EncodingType type;
+		public EncodingSelectItem(EncodingType type) {
+			this.type = type;
+		}
+		@Override
+		public String toString() {
+			return type.getName();
+		}
+	}
+
+	private static class ColorDepthSelectItem {
+		final int depth;
+		final String title;
+		public ColorDepthSelectItem(int depth, String title) {
+			this.depth = depth;
+			this.title = title;
+		}
+		@Override
+		public String toString() {
+			return title;
+		}
+	}
+
+	private JPanel createColorDepthPanel() {
+		JPanel colorDepthPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+		colorDepthPanel.setAlignmentX(LEFT_ALIGNMENT);
+		colorDepthPanel.add(new JLabel("Color format: "));
+
+		colorDepth = new JComboBox();
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_SERVER_SETTINGS,
+				"Server's default"));
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_24,
+				"16 777 216 colors"));
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_16,
+				"65 536 colors"));
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_8,
+				"256 colors"));
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_6,
+				"64 colors"));
+		colorDepth.addItem(new ColorDepthSelectItem(ProtocolSettings.COLOR_DEPTH_3,
+				"8 colors"));
+
+		colorDepthPanel.add(colorDepth);
+		colorDepth.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+                setJpegQualityPaneEnable();
+			}
+		});
+		return colorDepthPanel;
+	}
+
+	private void addJpegQualityLevelPane(JPanel encodingsPanel) {
+		useJpegQuality = new JCheckBox("Allow JPEG, set quality level:");
+		useJpegQuality.setAlignmentX(LEFT_ALIGNMENT);
+		encodingsPanel.add(useJpegQuality);
+
+		JPanel jpegQualityPane = new JPanel();
+		jpegQualityPane.setAlignmentX(LEFT_ALIGNMENT);
+		jpegQualityPoorLabel = new JLabel("poor");
+		jpegQualityPane.add(jpegQualityPoorLabel);
+		jpegQuality = new JSlider(1, 9, 9);
+		jpegQualityPane.add(jpegQuality);
+		jpegQuality.setPaintTicks(true);
+		jpegQuality.setMinorTickSpacing(1);
+		jpegQuality.setMajorTickSpacing(1);
+		jpegQuality.setPaintLabels(true);
+		jpegQuality.setSnapToTicks(true);
+		jpegQuality.setFont(
+				jpegQuality.getFont().deriveFont((float) 8));
+		jpegQualityBestLabel = new JLabel("best");
+		jpegQualityPane.add(jpegQualityBestLabel);
+		encodingsPanel.add(jpegQualityPane);
+
+		jpegQualityPoorLabel.setFont(jpegQualityPoorLabel.getFont().deriveFont((float) 10));
+		jpegQualityBestLabel.setFont(jpegQualityBestLabel.getFont().deriveFont((float) 10));
+
+		useJpegQuality.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				setJpegQualityPaneEnable();
+			}
+		});
+	}
+
+	protected void setJpegQualityPaneEnable() {
+		if (useJpegQuality != null && colorDepth != null) {
+			int depth = ((ColorDepthSelectItem)colorDepth.getSelectedItem()).depth;
+			setEnabled(whetherJpegQualityPaneBeEnabled(depth), useJpegQuality);
+			setEnabled(useJpegQuality.isSelected() && whetherJpegQualityPaneBeEnabled(depth),
+					jpegQuality, jpegQualityPoorLabel, jpegQualityBestLabel);
+		}
+	}
+
+    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);
+
+		JPanel compressionLevelPane = new JPanel();
+		compressionLevelPane.setAlignmentX(LEFT_ALIGNMENT);
+		compressionLevelFastLabel = new JLabel("fast");
+		compressionLevelPane.add(compressionLevelFastLabel);
+		compressionLevel = new JSlider(1, 9, 1);
+		compressionLevelPane.add(compressionLevel);
+		compressionLevel.setPaintTicks(true);
+		compressionLevel.setMinorTickSpacing(1);
+		compressionLevel.setMajorTickSpacing(1);
+		compressionLevel.setPaintLabels(true);
+		compressionLevel.setSnapToTicks(true);
+		compressionLevel.setFont(compressionLevel.getFont().deriveFont((float) 8));
+		compressionLevelBestLabel = new JLabel("best");
+		compressionLevelPane.add(compressionLevelBestLabel);
+		encodingsPanel.add(compressionLevelPane);
+
+		compressionLevelFastLabel.setFont(compressionLevelFastLabel.getFont().deriveFont((float) 10));
+		compressionLevelBestLabel.setFont(compressionLevelBestLabel.getFont().deriveFont((float) 10));
+
+		useCompressionLevel.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				setEnabled(useCompressionLevel.isSelected(),
+						compressionLevel, compressionLevelFastLabel, compressionLevelBestLabel);
+			}
+		});
+		setCompressionLevelPaneEnable();
+	}
+
+	protected void setCompressionLevelPaneEnable() {
+		setEnabled(useCompressionLevel.isSelected(),
+				compressionLevel, compressionLevelFastLabel, compressionLevelBestLabel);
+	}
+	private void setEnabled(boolean isEnabled, JComponent ... comp) {
+		for (JComponent c : comp) {
+			c.setEnabled(isEnabled);
+		}
+	}
+
+	private JPanel createLocalShapePanel() {
+		JPanel localCursorShapePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
+//		localCursorShapePanel.setLayout(new BoxLayout(localCursorShapePanel, BoxLayout.Y_AXIS));
+		localCursorShapePanel.setBorder(
+				BorderFactory.createTitledBorder(
+						BorderFactory.createEtchedBorder(), "Local cursor shape"));
+		Box localCursorShapeBox = Box.createVerticalBox();
+		localCursorShapePanel.add(localCursorShapeBox);
+
+        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(
+				BorderFactory.createTitledBorder(
+						BorderFactory.createEtchedBorder(), "Mouse Cursor"));
+		Box mouseCursorBox = Box.createVerticalBox();
+		mouseCursorPanel.add(mouseCursorBox);
+
+		ButtonGroup mouseCursorTrackGroup = new ButtonGroup();
+
+		mouseCursorTrackSelected = new RadioButtonSelectedState<LocalPointer>();
+		mouseCursorTrackMap = new HashMap<LocalPointer, JRadioButton>();
+
+		addRadioButton("Track remote cursor locally", LocalPointer.ON,
+				mouseCursorTrackSelected, mouseCursorTrackMap, mouseCursorBox,
+				mouseCursorTrackGroup);
+		addRadioButton("Let remote server deal with mouse cursor",
+				LocalPointer.OFF,
+				mouseCursorTrackSelected, mouseCursorTrackMap, mouseCursorBox,
+				mouseCursorTrackGroup);
+		addRadioButton("Don't show remote cursor", LocalPointer.HIDE,
+				mouseCursorTrackSelected, mouseCursorTrackMap, mouseCursorBox,
+				mouseCursorTrackGroup);
+		return mouseCursorPanel;
+	}
+
+	private static class RadioButtonSelectedState<T> {
+		private T state;
+
+		public void setSelected(T state) {
+			this.state = state;
+		}
+
+		public T getSelected() {
+			return state;
+		}
+
+	}
+
+	private <T> JRadioButton addRadioButton(String text, final T state,
+			final RadioButtonSelectedState<T> selected,
+			Map<T, JRadioButton> state2buttonMap, JComponent component, ButtonGroup group) {
+		JRadioButton radio = new JRadioButton(text);
+		radio.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				selected.setSelected(state);
+			}
+		});
+		component.add(radio);
+		group.add(radio);
+		state2buttonMap.put(state, radio);
+		return radio;
+	}
+
+	private void addButtons(final WindowListener onClose) {
+		JPanel buttonPanel = new JPanel();
+		JButton loginButton = new JButton("Ok");
+		buttonPanel.add(loginButton);
+		loginButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				setSettingsFromControls();
+				setVisible(false);
+			}
+		});
+
+		JButton closeButton = new JButton("Cancel");
+		buttonPanel.add(closeButton);
+		closeButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				onClose.windowClosing(null);
+			}
+		});
+		add(buttonPanel, BorderLayout.SOUTH);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/gui/PasswordDialog.java	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,115 @@
+// 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.ConnectionWorker;
+import com.glavsoft.viewer.swing.Utils;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+/**
+ * Dialog to ask password
+ */
+@SuppressWarnings("serial")
+public class PasswordDialog extends JDialog {
+
+	private String password = "";
+
+	private static final int PADDING = 4;
+	private final JLabel messageLabel;
+	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 VNC authentication");
+		pane.add(messageLabel);
+
+		JPanel passwordPanel = new JPanel();
+		passwordPanel.add(new JLabel("Password:"));
+		final JPasswordField passwordField = new JPasswordField("", 20);
+		passwordPanel.add(passwordField);
+		pane.add(passwordPanel);
+
+		JPanel buttonPanel = new JPanel();
+		JButton loginButton = new JButton("Login");
+		buttonPanel.add(loginButton);
+		loginButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				password = new String(passwordField.getPassword());
+				setVisible(false);
+			}
+		});
+
+		JButton closeButton = new JButton("Cancel");
+		buttonPanel.add(closeButton);
+		closeButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+                password = null;
+				setVisible(false);
+				onCancel.cancel();
+			}
+		});
+
+		pane.add(buttonPanel);
+
+		getRootPane().setDefaultButton(loginButton);
+        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 VNC authentication");
+		pack();
+	}
+
+	public String getPassword() {
+		return password;
+	}
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshConnectionManager.java	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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	Thu Sep 11 07:30:03 2014 +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-alt.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-close.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl-alt-del.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-file-transfer.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-info.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-new-connection.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-options.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-rec.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-refresh.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-save.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-win.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-100.png has changed
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/button-zoom-in.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-out.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
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-16x16.png has changed
Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-32x32.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/web/viewer-applet-example.html	Thu Sep 11 07:30:03 2014 +0900
@@ -0,0 +1,60 @@
+<!-- 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. -->
+<!-- ------------------------------------------------------------------------- -->
+<!--  -->
+
+<html>
+	<title>TightVNC desktop</title>
+	<body>
+		<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. -->
+			<param name="Port" value="5900" /> <!-- Port number to connect. Default: 5900 -->
+			<!--param name="Password" value="" /--> <!-- Password to the server (not recommended to use this parameter here) -->
+			<param name="OpenNewWindow" value="yes" /> <!-- yes/true or no/false. Default: yes/true -->
+			<param name="ShowControls" value="yes" /> <!-- yes/true or no/false. Default: yes/true -->
+			<param name="ViewOnly" value="no" /> <!-- yes/true or no/false. Default: no/false -->
+			<param name="AllowClipboardTransfer" value="yes" /> <!-- yes/true or no/false. Default: yes/true -->
+            <param name="RemoteCharset" value="standard" /> <!-- Charset encoding is used on remote system. Use this option to specify character encoding will be used for encoding clipboard text content to. Default value (when parameter is empty): local system default character encoding. Set the value to 'standard' for using 'Latin-1' charset which is only specified by rfb standard for clipboard transfers. -->
+
+			<param name="ShareDesktop" value="yes" /> <!-- yes/true or no/false. Default: yes/true -->
+			<param name="AllowCopyRect" value="yes" /> <!-- yes/true or no/false. Default: yes/true -->
+			<param name="Encoding" value="Tight" /> <!-- Possible values: "Tight", "Hextile", "ZRLE", and "Raw". Default: Tight -->
+			<param name="CompressionLevel" value="" /> <!-- 1-9 or empty. Empty means server default -->
+			<param name="JpegImageQuality" value="" /> <!-- 1-9, Lossless or empty. When param is set to "Lossless" no jpeg compression used. Empty means server default -->
+			<param name="LocalPointer" value="On" /> <!-- Possible values: on/yes/true (draw pointer locally), off/no/false (let server draw pointer), hide). Default: "On"-->
+			<param name="ConvertToASCII" value="no" /> <!-- Whether to convert keyboard input to ASCII ignoring locale. Possible values: yes/true, no/false). Default: "No"-->
+
+			<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% -->
+            <!--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>
+</html>
\ No newline at end of file