# HG changeset patch # User YU # Date 1410388203 -32400 # Node ID daa24f8a557b2302065dba06d3283e86348e9ef5 TightVNC original diff -r 000000000000 -r daa24f8a557b LICENSE.txt --- /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. + + + Copyright (C) + + 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. + + , 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. diff -r 000000000000 -r daa24f8a557b README.txt --- /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 diff -r 000000000000 -r daa24f8a557b build.gradle --- /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] +} diff -r 000000000000 -r daa24f8a557b src/libs/jsch-0.1.50.jar Binary file src/libs/jsch-0.1.50.jar has changed diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/core/SettingsChangedEvent.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/drawing/ColorDecoder.java --- /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)<= 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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/drawing/SoftCursor.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/AuthenticationFailedException.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/ClosedConnectionException.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/CommonException.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/CryptoException.java --- /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); + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/FatalException.java --- /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); + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/ProtocolException.java --- /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); + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/TransportException.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/UnsupportedProtocolVersionException.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/exceptions/UnsupportedSecurityTypeException.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/CapabilityContainer.java --- /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 caps = new HashMap(); + + 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 getEnabledEncodingTypes() { + Collection types = new LinkedList(); + 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()); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/ClipboardController.java --- /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); + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/IChangeSettingsListener.java --- /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); +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/IPasswordRetriever.java --- /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(); +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/IRepaintController.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/IRfbSessionListener.java --- /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); + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/RfbCapabilityInfo.java --- /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 + + "]"; + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/ClientCutTextMessage.java --- /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: ...]"; + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/ClientToServerMessage.java --- /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; +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/FramebufferUpdateRequestMessage.java --- /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 + "]"; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/KeyEventMessage.java --- /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 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)+")]"; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/PointerEventMessage.java --- /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 +"]"; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/SetEncodingsMessage.java --- /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 encodings; + + public SetEncodingsMessage(Set 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(); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/client/SetPixelFormatMessage.java --- /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(); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/EncodingType.java --- /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 ordinaryEncodings = new LinkedHashSet(); + 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 pseudoEncodings = new LinkedHashSet(); + static { + pseudoEncodings.add(RICH_CURSOR); + pseudoEncodings.add(CURSOR_POS); + pseudoEncodings.add(DESKTOP_SIZE); + } + + public static LinkedHashSet compressionEncodings = new LinkedHashSet(); + 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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/PixelFormat.java --- /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) + + "]"; + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/ServerInitMessage.java --- /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 + + "]"; + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/ByteBuffer.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/CopyRectDecoder.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/Decoder.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/DecodersContainer.java --- /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> knownDecoders = + new HashMap>(); + 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 decoders = + new HashMap(); + + 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 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(); + } + } + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/FramebufferUpdateRectangle.java --- /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 + + "]"; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/HextileDecoder.java --- /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); + } + } + + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/RREDecoder.java --- /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); + } + + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/RawDecoder.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/RichCursorDecoder.java --- /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 0; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/TightDecoder.java --- /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]; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/encoding/decoder/ZRLEDecoder.java --- /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 queue; + + public MessageQueue() { + queue = new LinkedBlockingQueue(); + } + + 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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/Protocol.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/ProtocolContext.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/ProtocolSettings.java --- /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 encodings; + private transient final List 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(); + 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 encodings = new LinkedHashSet(); + 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 encodings1, LinkedHashSet encodings2) { + if (null == encodings1 || encodings1.size() != encodings2.size()) return true; + Iterator it1 = encodings1.iterator(); + Iterator 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 + + '}'; + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/ReceiverTask.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/SenderTask.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/auth/AuthHandler.java --- /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; + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/auth/NoneAuthentication.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/auth/SecurityType.java --- /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 implementedSecurityTypes = + new LinkedHashMap() {{ + 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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/auth/TightAuthentication.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/auth/VncAuthentication.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/AuthenticationState.java --- /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"); + } + } + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/HandshakeState.java --- /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 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()); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/InitState.java --- /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); + } + + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/InitTightState.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/ProtocolState.java --- /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; + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/SecurityType33State.java --- /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)); + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/SecurityType37State.java --- /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); + } + } +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/rfb/protocol/state/SecurityTypeState.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/transport/Reader.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/transport/Writer.java --- /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 diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/utils/Keymap.java --- /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 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 toMap(int[][] keys) { + Map keyMap = new HashMap(); + 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; + } + +} diff -r 000000000000 -r daa24f8a557b src/main/java/com/glavsoft/utils/Strings.java --- /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); + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/AbstractConnectionWorkerFactory.java --- /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); +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/CancelConnectionException.java --- /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); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/ConnectionErrorException.java --- /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); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/ConnectionPresenter.java --- /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; + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/ConnectionWorker.java --- /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 { + /** + * 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(); +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/NetworkConnectionWorker.java --- /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 { + + void setConnectionParams(ConnectionParams connectionParams); + + void setPresenter(ConnectionPresenter presenter); + + void setHasSshSupport(boolean hasSshSupport); +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/RfbConnectionWorker.java --- /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 setWorkingSocket(Socket workingSocket); + + void setRfbSettings(ProtocolSettings rfbSettings); + + void setUiSettings(UiSettings uiSettings); + + void setConnectionString(String connectionString); + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/UiSettings.java --- /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 listeners = new LinkedList(); + 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() + + '}'; + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/UiSettingsData.java --- /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 diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/Viewer.java --- /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"; + } + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/cli/Parser.java --- /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 options = new LinkedHashMap(); + private final List plainOptions = new ArrayList(); + 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 registeredViews; + private final Map registeredModels; + static private Logger logger = Logger.getLogger(Presenter.class.getName()); + private Throwable savedInvocationTargetException; + + public Presenter() { + registeredViews = new HashMap(); + registeredModels = new HashMap(); + } + + 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 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 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 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 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 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 + } + } + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/mvp/View.java --- /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(); +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/ClipboardControllerImpl.java --- /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()); + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/ConnectionParams.java --- /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; + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/KeyEventListener.java --- /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(); + } + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/KeyboardConvertor.java --- /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 keyMap = new HashMap() {{ + 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; + } + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/LocalMouseCursorShape.java --- /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; + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/ModifierButtonEventListener.java --- /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 buttons = new HashMap(); + 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 diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/MouseEventListener.java --- /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 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; + } + + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/RendererImpl.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/SoftCursorImpl.java --- /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)); + + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/Surface.java --- /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); + } + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/SwingConnectionWorkerFactory.java --- /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; + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/SwingNetworkConnectionWorker.java --- /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 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 = "" +connectionParams.hostName + ":" + connectionParams.getPortNumber(); + if (connectionParams.useSsh()) { + s += " (via ssh://" + connectionParams.sshUserName + "@" + connectionParams.sshHostName + ":" + connectionParams.getSshPortNumber() + ")"; + } + + String message = "Trying to connect to " + s + ""; + 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 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); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/SwingRfbConnectionWorker.java --- /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 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 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(); + } + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/SwingViewerWindow.java --- /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 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(); + + 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 diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/SwingViewerWindowFactory.java --- /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; + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/Utils.java --- /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 icons; + + private static List getApplicationIcons() { + if (icons != null) { + return icons; + } + icons = new LinkedList(); + 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 cursorCash = new HashMap(); + 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 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); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/WrongParameterException.java --- /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; + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/gui/AutoCompletionComboEditorDocument.java --- /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()); + } + + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/gui/ConnectionView.java --- /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 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); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/gui/ConnectionsHistory.java --- /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 protocolSettingsMap; + private Map uiSettingsDataMap; + LinkedList connections; + + public ConnectionsHistory() { + logger = Logger.getLogger(getClass().getName()); + init(); + retrieve(); + } + + private void init() { + protocolSettingsMap = new HashMap(); + uiSettingsDataMap = new HashMap(); + connections = new LinkedList(); + } + + private void retrieve() { + Preferences connectionsHistoryNode; + try { + connectionsHistoryNode = getConnectionHistoryNode(); + } catch (AccessControlException ace) { + return; + } + try { + final String[] orderNums; + orderNums = connectionsHistoryNode.childrenNames(); + SortedMap conns = new TreeMap(); + HashSet uniques = new HashSet(); + 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 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 cpList = new LinkedList(); + 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(); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/gui/HostnameComboboxRenderer.java --- /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 = "" +cp.hostName + ":" + cp.getPortNumber(); + if (cp.useSsh()) { + s += " (via ssh://" + cp.sshUserName + "@" + cp.sshHostName + ":" + cp.getSshPortNumber() + ")"; + } + return s + ""; + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/gui/OptionsDialog.java --- /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 mouseCursorTrackSelected; + private Map 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 mouseCursorShapeSelected; + private HashMap 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(); + mouseCursorShapeMap = new HashMap(); + + 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(); + mouseCursorTrackMap = new HashMap(); + + 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 { + private T state; + + public void setSelected(T state) { + this.state = state; + } + + public T getSelected() { + return state; + } + + } + + private JRadioButton addRadioButton(String text, final T state, + final RadioButtonSelectedState selected, + Map 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); + } + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/gui/PasswordDialog.java --- /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; + } + + + +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshConnectionManager.java --- /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 diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SshKnownHostsManager.java --- /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(); +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/java/com/glavsoft/viewer/swing/ssh/SwingSshUserInfo.java --- /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()); + } +} diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-alt.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-alt.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-close.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-close.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl-alt-del.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl-alt-del.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-ctrl.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-file-transfer.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-file-transfer.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-info.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-info.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-new-connection.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-new-connection.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-options.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-options.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-rec.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-rec.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-refresh.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-refresh.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-save.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-save.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-win.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-win.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-100.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-100.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fit.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fit.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fullscreen.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-fullscreen.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-in.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-in.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-out.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/button-zoom-out.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-dot.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-dot.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-nocursor.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-nocursor.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-smalldot.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/cursor-smalldot.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-16x16.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-16x16.png has changed diff -r 000000000000 -r daa24f8a557b src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-32x32.png Binary file src/viewer_swing/resources/com/glavsoft/viewer/images/tightvnc-logo-32x32.png has changed diff -r 000000000000 -r daa24f8a557b src/web/viewer-applet-example.html --- /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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + TightVNC desktop + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ TightVNC Web Site + + \ No newline at end of file