# HG changeset patch # User e085711 # Date 1302580653 -32400 # Node ID e04119c40b9b63c6acea5d53a6cb56641d7800b1 upload all file of tighVNCClient diff -r 000000000000 -r e04119c40b9b .project --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.project Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,11 @@ + + + tightVNCClient + + + + + + + + diff -r 000000000000 -r e04119c40b9b src/AuthPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/AuthPanel.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,116 @@ +// +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// Copyright (C) 2002-2006 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +import java.awt.*; +import java.awt.event.*; + +// +// The panel which implements the user authentication scheme +// + +class AuthPanel extends Panel implements ActionListener { + + TextField passwordField; + Button okButton; + + // + // Constructor. + // + + public AuthPanel(VncViewer viewer) + { + Label titleLabel = new Label("VNC Authentication", Label.CENTER); + titleLabel.setFont(new Font("Helvetica", Font.BOLD, 18)); + + Label promptLabel = new Label("Password:", Label.CENTER); + + passwordField = new TextField(10); + passwordField.setForeground(Color.black); + passwordField.setBackground(Color.white); + passwordField.setEchoChar('*'); + + okButton = new Button("OK"); + + GridBagLayout gridbag = new GridBagLayout(); + GridBagConstraints gbc = new GridBagConstraints(); + + setLayout(gridbag); + + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.insets = new Insets(0,0,20,0); + gridbag.setConstraints(titleLabel,gbc); + add(titleLabel); + + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = 1; + gbc.insets = new Insets(0,0,0,0); + gridbag.setConstraints(promptLabel,gbc); + add(promptLabel); + + gridbag.setConstraints(passwordField,gbc); + add(passwordField); + passwordField.addActionListener(this); + + // gbc.ipady = 10; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(0,20,0,0); + gbc.ipadx = 30; + gridbag.setConstraints(okButton,gbc); + add(okButton); + okButton.addActionListener(this); + } + + // + // Move keyboard focus to the default object, that is, the password + // text field. + // + + public void moveFocusToDefaultField() + { + passwordField.requestFocus(); + } + + // + // This method is called when a button is pressed or return is + // pressed in the password text field. + // + + public synchronized void actionPerformed(ActionEvent evt) + { + if (evt.getSource() == passwordField || evt.getSource() == okButton) { + passwordField.setEnabled(false); + notify(); + } + } + + // + // Wait for user entering a password, and return it as String. + // + + public synchronized String getPassword() throws Exception + { + try { + wait(); + } catch (InterruptedException e) { } + return passwordField.getText(); + } + +} diff -r 000000000000 -r e04119c40b9b src/ButtonPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ButtonPanel.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,154 @@ +// +// Copyright (C) 2001,2002 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// ButtonPanel class implements panel with four buttons in the +// VNCViewer desktop window. +// + +import java.awt.*; +import java.awt.event.*; +import java.io.*; + +class ButtonPanel extends Panel implements ActionListener { + + VncViewer viewer; + Button disconnectButton; + Button optionsButton; + Button recordButton; + Button clipboardButton; + Button ctrlAltDelButton; + Button refreshButton; + + ButtonPanel(VncViewer v) { + viewer = v; + + setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + disconnectButton = new Button("Disconnect"); + disconnectButton.setEnabled(false); + add(disconnectButton); + disconnectButton.addActionListener(this); + optionsButton = new Button("Options"); + add(optionsButton); + optionsButton.addActionListener(this); + clipboardButton = new Button("Clipboard"); + clipboardButton.setEnabled(false); + add(clipboardButton); + clipboardButton.addActionListener(this); + if (viewer.rec != null) { + recordButton = new Button("Record"); + add(recordButton); + recordButton.addActionListener(this); + } + ctrlAltDelButton = new Button("Send Ctrl-Alt-Del"); + ctrlAltDelButton.setEnabled(false); + add(ctrlAltDelButton); + ctrlAltDelButton.addActionListener(this); + refreshButton = new Button("Refresh"); + refreshButton.setEnabled(false); + add(refreshButton); + refreshButton.addActionListener(this); + } + + // + // Enable buttons on successful connection. + // + + public void enableButtons() { + disconnectButton.setEnabled(true); + clipboardButton.setEnabled(true); + refreshButton.setEnabled(true); + } + + // + // Disable all buttons on disconnect. + // + + public void disableButtonsOnDisconnect() { + remove(disconnectButton); + disconnectButton = new Button("Hide desktop"); + disconnectButton.setEnabled(true); + add(disconnectButton, 0); + disconnectButton.addActionListener(this); + + optionsButton.setEnabled(false); + clipboardButton.setEnabled(false); + ctrlAltDelButton.setEnabled(false); + refreshButton.setEnabled(false); + + validate(); + } + + // + // Enable/disable controls that should not be available in view-only + // mode. + // + + public void enableRemoteAccessControls(boolean enable) { + ctrlAltDelButton.setEnabled(enable); + } + + // + // Event processing. + // + + public void actionPerformed(ActionEvent evt) { + + viewer.moveFocusToDesktop(); + + if (evt.getSource() == disconnectButton) { + viewer.disconnect(); + + } else if (evt.getSource() == optionsButton) { + viewer.options.setVisible(!viewer.options.isVisible()); + + } else if (evt.getSource() == recordButton) { + viewer.rec.setVisible(!viewer.rec.isVisible()); + + } else if (evt.getSource() == clipboardButton) { + viewer.clipboard.setVisible(!viewer.clipboard.isVisible()); + + } else if (evt.getSource() == ctrlAltDelButton) { + try { + final int modifiers = InputEvent.CTRL_MASK | InputEvent.ALT_MASK; + + KeyEvent ctrlAltDelEvent = + new KeyEvent(this, KeyEvent.KEY_PRESSED, 0, modifiers, 127); + viewer.rfb.writeKeyEvent(ctrlAltDelEvent); + + ctrlAltDelEvent = + new KeyEvent(this, KeyEvent.KEY_RELEASED, 0, modifiers, 127); + viewer.rfb.writeKeyEvent(ctrlAltDelEvent); + + } catch (IOException e) { + e.printStackTrace(); + } + } else if (evt.getSource() == refreshButton) { + try { + RfbProto rfb = viewer.rfb; + rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth, + rfb.framebufferHeight, false); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} + diff -r 000000000000 -r e04119c40b9b src/CapabilityInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/CapabilityInfo.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,87 @@ +// +// Copyright (C) 2003 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// CapabilityInfo.java - A class to hold information about a +// particular capability as used in the RFB protocol 3.130. +// + +class CapabilityInfo { + + // Public methods + + public CapabilityInfo(int code, + String vendorSignature, + String nameSignature, + String description) { + this.code = code; + this.vendorSignature = vendorSignature; + this.nameSignature = nameSignature; + this.description = description; + enabled = false; + } + + public CapabilityInfo(int code, + byte[] vendorSignature, + byte[] nameSignature) { + this.code = code; + this.vendorSignature = new String(vendorSignature); + this.nameSignature = new String(nameSignature); + this.description = null; + enabled = false; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public boolean isEnabled() { + return enabled; + } + + public void enable() { + enabled = true; + } + + public boolean equals(CapabilityInfo other) { + return (other != null && this.code == other.code && + this.vendorSignature.equals(other.vendorSignature) && + this.nameSignature.equals(other.nameSignature)); + } + + public boolean enableIfEquals(CapabilityInfo other) { + if (this.equals(other)) + enable(); + + return isEnabled(); + } + + // Protected data + + protected int code; + protected String vendorSignature; + protected String nameSignature; + + protected String description; + protected boolean enabled; +} diff -r 000000000000 -r e04119c40b9b src/CapsContainer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/CapsContainer.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,103 @@ +// +// Copyright (C) 2003 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// CapsContainer.java - A container of capabilities as used in the RFB +// protocol 3.130 +// + +import java.util.Vector; +import java.util.Hashtable; + +class CapsContainer { + + // Public methods + + public CapsContainer() { + infoMap = new Hashtable(64, (float)0.25); + orderedList = new Vector(32, 8); + } + + public void add(CapabilityInfo capinfo) { + Integer key = new Integer(capinfo.getCode()); + infoMap.put(key, capinfo); + } + + public void add(int code, String vendor, String name, String desc) { + Integer key = new Integer(code); + infoMap.put(key, new CapabilityInfo(code, vendor, name, desc)); + } + + public boolean isKnown(int code) { + return infoMap.containsKey(new Integer(code)); + } + + public CapabilityInfo getInfo(int code) { + return (CapabilityInfo)infoMap.get(new Integer(code)); + } + + public String getDescription(int code) { + CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code)); + if (capinfo == null) + return null; + + return capinfo.getDescription(); + } + + public boolean enable(CapabilityInfo other) { + Integer key = new Integer(other.getCode()); + CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(key); + if (capinfo == null) + return false; + + boolean enabled = capinfo.enableIfEquals(other); + if (enabled) + orderedList.addElement(key); + + return enabled; + } + + public boolean isEnabled(int code) { + CapabilityInfo capinfo = (CapabilityInfo)infoMap.get(new Integer(code)); + if (capinfo == null) + return false; + + return capinfo.isEnabled(); + } + + public int numEnabled() { + return orderedList.size(); + } + + public int getByOrder(int idx) { + int code; + try { + code = ((Integer)orderedList.elementAt(idx)).intValue(); + } catch (ArrayIndexOutOfBoundsException e) { + code = 0; + } + return code; + } + + // Protected data + + protected Hashtable infoMap; + protected Vector orderedList; +} + diff -r 000000000000 -r e04119c40b9b src/ClipboardFrame.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ClipboardFrame.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,133 @@ +// +// Copyright (C) 2001 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// Clipboard frame. +// + +import java.awt.*; +import java.awt.event.*; + +class ClipboardFrame extends Frame + implements WindowListener, ActionListener { + + TextArea textArea; + Button clearButton, closeButton; + String selection; + VncViewer viewer; + + // + // Constructor. + // + + ClipboardFrame(VncViewer v) { + super("TightVNC Clipboard"); + + viewer = v; + + GridBagLayout gridbag = new GridBagLayout(); + setLayout(gridbag); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1.0; + + textArea = new TextArea(5, 40); + gridbag.setConstraints(textArea, gbc); + add(textArea); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.gridwidth = 1; + + clearButton = new Button("Clear"); + gridbag.setConstraints(clearButton, gbc); + add(clearButton); + clearButton.addActionListener(this); + + closeButton = new Button("Close"); + gridbag.setConstraints(closeButton, gbc); + add(closeButton); + closeButton.addActionListener(this); + + pack(); + + addWindowListener(this); + } + + + // + // Set the cut text from the RFB server. + // + + void setCutText(String text) { + selection = text; + textArea.setText(text); + if (isVisible()) { + textArea.selectAll(); + } + } + + + // + // When the focus leaves the window, see if we have new cut text and + // if so send it to the RFB server. + // + + public void windowDeactivated (WindowEvent evt) { + if (selection != null && !selection.equals(textArea.getText())) { + selection = textArea.getText(); + viewer.setCutText(selection); + } + } + + // + // Close our window properly. + // + + public void windowClosing(WindowEvent evt) { + setVisible(false); + } + + // + // Ignore window events we're not interested in. + // + + public void windowActivated(WindowEvent evt) {} + public void windowOpened(WindowEvent evt) {} + public void windowClosed(WindowEvent evt) {} + public void windowIconified(WindowEvent evt) {} + public void windowDeiconified(WindowEvent evt) {} + + + // + // Respond to button presses + // + + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == clearButton) { + textArea.setText(""); + } else if (evt.getSource() == closeButton) { + setVisible(false); + } + } +} diff -r 000000000000 -r e04119c40b9b src/DesCipher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/DesCipher.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,496 @@ +// +// This DES class has been extracted from package Acme.Crypto for use in VNC. +// The bytebit[] array has been reversed so that the most significant bit +// in each byte of the key is ignored, not the least significant. Also the +// unnecessary odd parity code has been removed. +// +// These changes are: +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This software 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. +// + +// DesCipher - the DES encryption method +// +// The meat of this code is by Dave Zimmerman , and is: +// +// Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved. +// +// Permission to use, copy, modify, and distribute this software +// and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and +// without fee is hereby granted, provided that this copyright notice is kept +// intact. +// +// WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY +// OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE +// FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR +// DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. +// +// THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE +// CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE +// PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT +// NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE +// SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE +// SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE +// PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP +// SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR +// HIGH RISK ACTIVITIES. +// +// +// The rest is: +// +// Copyright (C) 1996 by Jef Poskanzer . All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + + +import java.io.*; + +/// The DES encryption method. +//

+// This is surprisingly fast, for pure Java. On a SPARC 20, wrapped +// in Acme.Crypto.EncryptedOutputStream or Acme.Crypto.EncryptedInputStream, +// it does around 7000 bytes/second. +//

+// Most of this code is by Dave Zimmerman , and is +// Copyright (c) 1996 Widget Workshop, Inc. See the source file for details. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @see Des3Cipher +// @see EncryptedOutputStream +// @see EncryptedInputStream + +public class DesCipher + { + + // Constructor, byte-array key. + public DesCipher( byte[] key ) + { + setKey( key ); + } + + // Key routines. + + private int[] encryptKeys = new int[32]; + private int[] decryptKeys = new int[32]; + + /// Set the key. + public void setKey( byte[] key ) + { + deskey( key, true, encryptKeys ); + deskey( key, false, decryptKeys ); + } + + // Turn an 8-byte key into internal keys. + private void deskey( byte[] keyBlock, boolean encrypting, int[] KnL ) + { + int i, j, l, m, n; + int[] pc1m = new int[56]; + int[] pcr = new int[56]; + int[] kn = new int[32]; + + for ( j = 0; j < 56; ++j ) + { + l = pc1[j]; + m = l & 07; + pc1m[j] = ( (keyBlock[l >>> 3] & bytebit[m]) != 0 )? 1: 0; + } + + for ( i = 0; i < 16; ++i ) + { + if ( encrypting ) + m = i << 1; + else + m = (15-i) << 1; + n = m+1; + kn[m] = kn[n] = 0; + for ( j = 0; j < 28; ++j ) + { + l = j+totrot[i]; + if ( l < 28 ) + pcr[j] = pc1m[l]; + else + pcr[j] = pc1m[l-28]; + } + for ( j=28; j < 56; ++j ) + { + l = j+totrot[i]; + if ( l < 56 ) + pcr[j] = pc1m[l]; + else + pcr[j] = pc1m[l-28]; + } + for ( j = 0; j < 24; ++j ) + { + if ( pcr[pc2[j]] != 0 ) + kn[m] |= bigbyte[j]; + if ( pcr[pc2[j+24]] != 0 ) + kn[n] |= bigbyte[j]; + } + } + cookey( kn, KnL ); + } + + private void cookey( int[] raw, int KnL[] ) + { + int raw0, raw1; + int rawi, KnLi; + int i; + + for ( i = 0, rawi = 0, KnLi = 0; i < 16; ++i ) + { + raw0 = raw[rawi++]; + raw1 = raw[rawi++]; + KnL[KnLi] = (raw0 & 0x00fc0000) << 6; + KnL[KnLi] |= (raw0 & 0x00000fc0) << 10; + KnL[KnLi] |= (raw1 & 0x00fc0000) >>> 10; + KnL[KnLi] |= (raw1 & 0x00000fc0) >>> 6; + ++KnLi; + KnL[KnLi] = (raw0 & 0x0003f000) << 12; + KnL[KnLi] |= (raw0 & 0x0000003f) << 16; + KnL[KnLi] |= (raw1 & 0x0003f000) >>> 4; + KnL[KnLi] |= (raw1 & 0x0000003f); + ++KnLi; + } + } + + + // Block encryption routines. + + private int[] tempInts = new int[2]; + + /// Encrypt a block of eight bytes. + public void encrypt( byte[] clearText, int clearOff, byte[] cipherText, int cipherOff ) + { + squashBytesToInts( clearText, clearOff, tempInts, 0, 2 ); + des( tempInts, tempInts, encryptKeys ); + spreadIntsToBytes( tempInts, 0, cipherText, cipherOff, 2 ); + } + + /// Decrypt a block of eight bytes. + public void decrypt( byte[] cipherText, int cipherOff, byte[] clearText, int clearOff ) + { + squashBytesToInts( cipherText, cipherOff, tempInts, 0, 2 ); + des( tempInts, tempInts, decryptKeys ); + spreadIntsToBytes( tempInts, 0, clearText, clearOff, 2 ); + } + + // The DES function. + private void des( int[] inInts, int[] outInts, int[] keys ) + { + int fval, work, right, leftt; + int round; + int keysi = 0; + + leftt = inInts[0]; + right = inInts[1]; + + work = ((leftt >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + leftt ^= (work << 4); + + work = ((leftt >>> 16) ^ right) & 0x0000ffff; + right ^= work; + leftt ^= (work << 16); + + work = ((right >>> 2) ^ leftt) & 0x33333333; + leftt ^= work; + right ^= (work << 2); + + work = ((right >>> 8) ^ leftt) & 0x00ff00ff; + leftt ^= work; + right ^= (work << 8); + right = (right << 1) | ((right >>> 31) & 1); + + work = (leftt ^ right) & 0xaaaaaaaa; + leftt ^= work; + right ^= work; + leftt = (leftt << 1) | ((leftt >>> 31) & 1); + + for ( round = 0; round < 8; ++round ) + { + work = (right << 28) | (right >>> 4); + work ^= keys[keysi++]; + fval = SP7[ work & 0x0000003f ]; + fval |= SP5[(work >>> 8) & 0x0000003f ]; + fval |= SP3[(work >>> 16) & 0x0000003f ]; + fval |= SP1[(work >>> 24) & 0x0000003f ]; + work = right ^ keys[keysi++]; + fval |= SP8[ work & 0x0000003f ]; + fval |= SP6[(work >>> 8) & 0x0000003f ]; + fval |= SP4[(work >>> 16) & 0x0000003f ]; + fval |= SP2[(work >>> 24) & 0x0000003f ]; + leftt ^= fval; + work = (leftt << 28) | (leftt >>> 4); + work ^= keys[keysi++]; + fval = SP7[ work & 0x0000003f ]; + fval |= SP5[(work >>> 8) & 0x0000003f ]; + fval |= SP3[(work >>> 16) & 0x0000003f ]; + fval |= SP1[(work >>> 24) & 0x0000003f ]; + work = leftt ^ keys[keysi++]; + fval |= SP8[ work & 0x0000003f ]; + fval |= SP6[(work >>> 8) & 0x0000003f ]; + fval |= SP4[(work >>> 16) & 0x0000003f ]; + fval |= SP2[(work >>> 24) & 0x0000003f ]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (leftt ^ right) & 0xaaaaaaaa; + leftt ^= work; + right ^= work; + leftt = (leftt << 31) | (leftt >>> 1); + work = ((leftt >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + leftt ^= (work << 8); + work = ((leftt >>> 2) ^ right) & 0x33333333; + right ^= work; + leftt ^= (work << 2); + work = ((right >>> 16) ^ leftt) & 0x0000ffff; + leftt ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ leftt) & 0x0f0f0f0f; + leftt ^= work; + right ^= (work << 4); + outInts[0] = right; + outInts[1] = leftt; + } + + + // Tables, permutations, S-boxes, etc. + + private static byte[] bytebit = { + (byte)0x01, (byte)0x02, (byte)0x04, (byte)0x08, + (byte)0x10, (byte)0x20, (byte)0x40, (byte)0x80 + }; + private static int[] bigbyte = { + 0x800000, 0x400000, 0x200000, 0x100000, + 0x080000, 0x040000, 0x020000, 0x010000, + 0x008000, 0x004000, 0x002000, 0x001000, + 0x000800, 0x000400, 0x000200, 0x000100, + 0x000080, 0x000040, 0x000020, 0x000010, + 0x000008, 0x000004, 0x000002, 0x000001 + }; + private static byte[] pc1 = { + (byte)56, (byte)48, (byte)40, (byte)32, (byte)24, (byte)16, (byte) 8, + (byte) 0, (byte)57, (byte)49, (byte)41, (byte)33, (byte)25, (byte)17, + (byte) 9, (byte) 1, (byte)58, (byte)50, (byte)42, (byte)34, (byte)26, + (byte)18, (byte)10, (byte) 2, (byte)59, (byte)51, (byte)43, (byte)35, + (byte)62, (byte)54, (byte)46, (byte)38, (byte)30, (byte)22, (byte)14, + (byte) 6, (byte)61, (byte)53, (byte)45, (byte)37, (byte)29, (byte)21, + (byte)13, (byte) 5, (byte)60, (byte)52, (byte)44, (byte)36, (byte)28, + (byte)20, (byte)12, (byte) 4, (byte)27, (byte)19, (byte)11, (byte)3 + }; + private static int[] totrot = { + 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 + }; + + private static byte[] pc2 = { + (byte)13, (byte)16, (byte)10, (byte)23, (byte) 0, (byte) 4, + (byte) 2, (byte)27, (byte)14, (byte) 5, (byte)20, (byte) 9, + (byte)22, (byte)18, (byte)11, (byte)3 , (byte)25, (byte) 7, + (byte)15, (byte) 6, (byte)26, (byte)19, (byte)12, (byte) 1, + (byte)40, (byte)51, (byte)30, (byte)36, (byte)46, (byte)54, + (byte)29, (byte)39, (byte)50, (byte)44, (byte)32, (byte)47, + (byte)43, (byte)48, (byte)38, (byte)55, (byte)33, (byte)52, + (byte)45, (byte)41, (byte)49, (byte)35, (byte)28, (byte)31, + }; + + private static int[] SP1 = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 + }; + private static int[] SP2 = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 + }; + private static int[] SP3 = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 + }; + private static int[] SP4 = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 + }; + private static int[] SP5 = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 + }; + private static int[] SP6 = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 + }; + private static int[] SP7 = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 + }; + private static int[] SP8 = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 + }; + + // Routines taken from other parts of the Acme utilities. + + /// Squash bytes down to ints. + public static void squashBytesToInts( byte[] inBytes, int inOff, int[] outInts, int outOff, int intLen ) + { + for ( int i = 0; i < intLen; ++i ) + outInts[outOff + i] = + ( ( inBytes[inOff + i * 4 ] & 0xff ) << 24 ) | + ( ( inBytes[inOff + i * 4 + 1] & 0xff ) << 16 ) | + ( ( inBytes[inOff + i * 4 + 2] & 0xff ) << 8 ) | + ( inBytes[inOff + i * 4 + 3] & 0xff ); + } + + /// Spread ints into bytes. + public static void spreadIntsToBytes( int[] inInts, int inOff, byte[] outBytes, int outOff, int intLen ) + { + for ( int i = 0; i < intLen; ++i ) + { + outBytes[outOff + i * 4 ] = (byte) ( inInts[inOff + i] >>> 24 ); + outBytes[outOff + i * 4 + 1] = (byte) ( inInts[inOff + i] >>> 16 ); + outBytes[outOff + i * 4 + 2] = (byte) ( inInts[inOff + i] >>> 8 ); + outBytes[outOff + i * 4 + 3] = (byte) inInts[inOff + i]; + } + } + } diff -r 000000000000 -r e04119c40b9b src/HTTPConnectSocket.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/HTTPConnectSocket.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,59 @@ +// +// Copyright (C) 2002 Constantin Kaplinsky, Inc. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// HTTPConnectSocket.java together with HTTPConnectSocketFactory.java +// implement an alternate way to connect to VNC servers via one or two +// HTTP proxies supporting the HTTP CONNECT method. +// + +import java.net.*; +import java.io.*; + +class HTTPConnectSocket extends Socket { + + public HTTPConnectSocket(String host, int port, + String proxyHost, int proxyPort) + throws IOException { + + // Connect to the specified HTTP proxy + super(proxyHost, proxyPort); + + // Send the CONNECT request + getOutputStream().write(("CONNECT " + host + ":" + port + + " HTTP/1.0\r\n\r\n").getBytes()); + + // Read the first line of the response + DataInputStream is = new DataInputStream(getInputStream()); + String str = is.readLine(); + + // Check the HTTP error code -- it should be "200" on success + if (!str.startsWith("HTTP/1.0 200 ")) { + if (str.startsWith("HTTP/1.0 ")) + str = str.substring(9); + throw new IOException("Proxy reports \"" + str + "\""); + } + + // Success -- skip remaining HTTP headers + do { + str = is.readLine(); + } while (str.length() != 0); + } +} + diff -r 000000000000 -r e04119c40b9b src/HTTPConnectSocketFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/HTTPConnectSocketFactory.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,86 @@ +// +// Copyright (C) 2002 Constantin Kaplinsky, Inc. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// HTTPConnectSocketFactory.java together with HTTPConnectSocket.java +// implement an alternate way to connect to VNC servers via one or two +// HTTP proxies supporting the HTTP CONNECT method. +// + +import java.applet.*; +import java.net.*; +import java.io.*; + +class HTTPConnectSocketFactory implements SocketFactory { + + public Socket createSocket(String host, int port, Applet applet) + throws IOException { + + return createSocket(host, port, + applet.getParameter("PROXYHOST1"), + applet.getParameter("PROXYPORT1")); + } + + public Socket createSocket(String host, int port, String[] args) + throws IOException { + + return createSocket(host, port, + readArg(args, "PROXYHOST1"), + readArg(args, "PROXYPORT1")); + } + + public Socket createSocket(String host, int port, + String proxyHost, String proxyPortStr) + throws IOException { + + int proxyPort = 0; + if (proxyPortStr != null) { + try { + proxyPort = Integer.parseInt(proxyPortStr); + } catch (NumberFormatException e) { } + } + + if (proxyHost == null || proxyPort == 0) { + System.out.println("Incomplete parameter list for HTTPConnectSocket"); + return new Socket(host, port); + } + + System.out.println("HTTP CONNECT via proxy " + proxyHost + + " port " + proxyPort); + HTTPConnectSocket s = + new HTTPConnectSocket(host, port, proxyHost, proxyPort); + + return (Socket)s; + } + + private String readArg(String[] args, String name) { + + for (int i = 0; i < args.length; i += 2) { + if (args[i].equalsIgnoreCase(name)) { + try { + return args[i+1]; + } catch (Exception e) { + return null; + } + } + } + return null; + } +} + diff -r 000000000000 -r e04119c40b9b src/InStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/InStream.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,177 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// +// rdr::InStream marshalls data from a buffer stored in RDR (RFB Data +// Representation). +// + +abstract public class InStream { + + // check() ensures there is buffer data for at least one item of size + // itemSize bytes. Returns the number of items in the buffer (up to a + // maximum of nItems). + + public final int check(int itemSize, int nItems) throws Exception { + if (ptr + itemSize * nItems > end) { + if (ptr + itemSize > end) + return overrun(itemSize, nItems); + + nItems = (end - ptr) / itemSize; + } + return nItems; + } + + public final void check(int itemSize) throws Exception { + if (ptr + itemSize > end) + overrun(itemSize, 1); + } + + // readU/SN() methods read unsigned and signed N-bit integers. + + public final int readS8() throws Exception { + check(1); return b[ptr++]; + } + + public final int readS16() throws Exception { + check(2); int b0 = b[ptr++]; + int b1 = b[ptr++] & 0xff; return b0 << 8 | b1; + } + + public final int readS32() throws Exception { + check(4); int b0 = b[ptr++]; + int b1 = b[ptr++] & 0xff; + int b2 = b[ptr++] & 0xff; + int b3 = b[ptr++] & 0xff; + return b0 << 24 | b1 << 16 | b2 << 8 | b3; + } + + public final int readU8() throws Exception { + return readS8() & 0xff; + } + + public final int readU16() throws Exception { + return readS16() & 0xffff; + } + + public final int readU32() throws Exception { + return readS32() & 0xffffffff; + } + + // readString() reads a string - a U32 length followed by the data. + + public final String readString() throws Exception { + int len = readU32(); + if (len > maxStringLength) + throw new Exception("InStream max string length exceeded"); + + char[] str = new char[len]; + int i = 0; + while (i < len) { + int j = i + check(1, len - i); + while (i < j) { + str[i++] = (char)b[ptr++]; + } + } + + return new String(str); + } + + // maxStringLength protects against allocating a huge buffer. Set it + // higher if you need longer strings. + + public static int maxStringLength = 65535; + + public final void skip(int bytes) throws Exception { + while (bytes > 0) { + int n = check(1, bytes); + ptr += n; + bytes -= n; + } + } + + // readBytes() reads an exact number of bytes into an array at an offset. + + public void readBytes(byte[] data, int offset, int length) throws Exception { + int offsetEnd = offset + length; + while (offset < offsetEnd) { + int n = check(1, offsetEnd - offset); + System.arraycopy(b, ptr, data, offset, n); + ptr += n; + offset += n; + } + } + + // readOpaqueN() reads a quantity "without byte-swapping". Because java has + // no byte-ordering, we just use big-endian. + + public final int readOpaque8() throws Exception { + return readU8(); + } + + public final int readOpaque16() throws Exception { + return readU16(); + } + + public final int readOpaque32() throws Exception { + return readU32(); + } + + public final int readOpaque24A() throws Exception { + check(3); int b0 = b[ptr++]; + int b1 = b[ptr++]; int b2 = b[ptr++]; + return b0 << 24 | b1 << 16 | b2 << 8; + } + + public final int readOpaque24B() throws Exception { + check(3); int b0 = b[ptr++]; + int b1 = b[ptr++]; int b2 = b[ptr++]; + return b0 << 16 | b1 << 8 | b2; + } + + // pos() returns the position in the stream. + + abstract public int pos(); + + // bytesAvailable() returns true if at least one byte can be read from the + // stream without blocking. i.e. if false is returned then readU8() would + // block. + + public boolean bytesAvailable() { return end != ptr; } + + // getbuf(), getptr(), getend() and setptr() are "dirty" methods which allow + // you to manipulate the buffer directly. This is useful for a stream which + // is a wrapper around an underlying stream. + + public final byte[] getbuf() { return b; } + public final int getptr() { return ptr; } + public final int getend() { return end; } + public final void setptr(int p) { ptr = p; } + + // overrun() is implemented by a derived class to cope with buffer overrun. + // It ensures there are at least itemSize bytes of buffer data. Returns + // the number of items in the buffer (up to a maximum of nItems). itemSize + // is supposed to be "small" (a few bytes). + + abstract protected int overrun(int itemSize, int nItems) throws Exception; + + protected InStream() {} + protected byte[] b; + protected int ptr; + protected int end; +} diff -r 000000000000 -r e04119c40b9b src/MemInStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/MemInStream.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,32 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +public class MemInStream extends InStream { + + public MemInStream(byte[] data, int offset, int len) { + b = data; + ptr = offset; + end = offset + len; + } + + public int pos() { return ptr; } + + protected int overrun(int itemSize, int nItems) throws Exception { + throw new Exception("MemInStream overrun: end of stream"); + } +} diff -r 000000000000 -r e04119c40b9b src/OptionsFrame.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/OptionsFrame.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,405 @@ +// +// Copyright (C) 2001 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 2001 Constantin Kaplinsky. All Rights Reserved. +// Copyright (C) 2000 Tridia Corporation. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// Options frame. +// +// This deals with all the options the user can play with. +// It sets the encodings array and some booleans. +// + +import java.awt.*; +import java.awt.event.*; + +class OptionsFrame extends Frame + implements WindowListener, ActionListener, ItemListener { + + static String[] names = { + "Encoding", + "Compression level", + "JPEG image quality", + "Cursor shape updates", + "Use CopyRect", + "Restricted colors", + "Mouse buttons 2 and 3", + "View only", + "Scale remote cursor", + "Share desktop", + }; + + static String[][] values = { + { "Auto", "Raw", "RRE", "CoRRE", "Hextile", "Zlib", "Tight", "ZRLE" }, + { "Default", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, + { "JPEG off", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, + { "Enable", "Ignore", "Disable" }, + { "Yes", "No" }, + { "Yes", "No" }, + { "Normal", "Reversed" }, + { "Yes", "No" }, + { "No", "50%", "75%", "125%", "150%" }, + { "Yes", "No" }, + }; + + final int + encodingIndex = 0, + compressLevelIndex = 1, + jpegQualityIndex = 2, + cursorUpdatesIndex = 3, + useCopyRectIndex = 4, + eightBitColorsIndex = 5, + mouseButtonIndex = 6, + viewOnlyIndex = 7, + scaleCursorIndex = 8, + shareDesktopIndex = 9; + + Label[] labels = new Label[names.length]; + Choice[] choices = new Choice[names.length]; + Button closeButton; + VncViewer viewer; + + + // + // The actual data which other classes look at: + // + + int preferredEncoding; + int compressLevel; + int jpegQuality; + boolean useCopyRect; + boolean requestCursorUpdates; + boolean ignoreCursorUpdates; + + boolean eightBitColors; + + boolean reverseMouseButtons2And3; + boolean shareDesktop; + boolean viewOnly; + int scaleCursor; + + boolean autoScale; + int scalingFactor; + + // + // Constructor. Set up the labels and choices from the names and values + // arrays. + // + + OptionsFrame(VncViewer v) { + super("TightVNC Options"); + + viewer = v; + + GridBagLayout gridbag = new GridBagLayout(); + setLayout(gridbag); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.BOTH; + + for (int i = 0; i < names.length; i++) { + labels[i] = new Label(names[i]); + gbc.gridwidth = 1; + gridbag.setConstraints(labels[i],gbc); + add(labels[i]); + + choices[i] = new Choice(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(choices[i],gbc); + add(choices[i]); + choices[i].addItemListener(this); + + for (int j = 0; j < values[i].length; j++) { + choices[i].addItem(values[i][j]); + } + } + + closeButton = new Button("Close"); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(closeButton, gbc); + add(closeButton); + closeButton.addActionListener(this); + + pack(); + + addWindowListener(this); + + // Set up defaults + + choices[encodingIndex].select("Auto"); + choices[compressLevelIndex].select("Default"); + choices[jpegQualityIndex].select("6"); + choices[cursorUpdatesIndex].select("Enable"); + choices[useCopyRectIndex].select("Yes"); + choices[eightBitColorsIndex].select("No"); + choices[mouseButtonIndex].select("Normal"); + choices[viewOnlyIndex].select("No"); + choices[scaleCursorIndex].select("No"); + choices[shareDesktopIndex].select("Yes"); + + // But let them be overridden by parameters + + for (int i = 0; i < names.length; i++) { + String s = viewer.readParameter(names[i], false); + if (s != null) { + for (int j = 0; j < values[i].length; j++) { + if (s.equalsIgnoreCase(values[i][j])) { + choices[i].select(j); + } + } + } + } + + // FIXME: Provide some sort of GUI for "Scaling Factor". + + autoScale = false; + scalingFactor = 100; + String s = viewer.readParameter("Scaling Factor", false); + if (s != null) { + if (s.equalsIgnoreCase("Auto")) { + autoScale = true; + } else { + // Remove the '%' char at the end of string if present. + if (s.charAt(s.length() - 1) == '%') { + s = s.substring(0, s.length() - 1); + } + // Convert to an integer. + try { + scalingFactor = Integer.parseInt(s); + } + catch (NumberFormatException e) { + scalingFactor = 100; + } + // Make sure scalingFactor is in the range of [1..1000]. + if (scalingFactor < 1) { + scalingFactor = 1; + } else if (scalingFactor > 1000) { + scalingFactor = 1000; + } + } + } + + // Make the booleans and encodings array correspond to the state of the GUI + + setEncodings(); + setColorFormat(); + setOtherOptions(); + } + + + // + // Disable the shareDesktop option + // + + void disableShareDesktop() { + labels[shareDesktopIndex].setEnabled(false); + choices[shareDesktopIndex].setEnabled(false); + } + + // + // setEncodings looks at the encoding, compression level, JPEG + // quality level, cursor shape updates and copyRect choices and sets + // corresponding variables properly. Then it calls the VncViewer's + // setEncodings method to send a SetEncodings message to the RFB + // server. + // + + void setEncodings() { + useCopyRect = choices[useCopyRectIndex].getSelectedItem().equals("Yes"); + + preferredEncoding = RfbProto.EncodingRaw; + boolean enableCompressLevel = false; + + if (choices[encodingIndex].getSelectedItem().equals("RRE")) { + preferredEncoding = RfbProto.EncodingRRE; + } else if (choices[encodingIndex].getSelectedItem().equals("CoRRE")) { + preferredEncoding = RfbProto.EncodingCoRRE; + } else if (choices[encodingIndex].getSelectedItem().equals("Hextile")) { + preferredEncoding = RfbProto.EncodingHextile; + } else if (choices[encodingIndex].getSelectedItem().equals("ZRLE")) { + preferredEncoding = RfbProto.EncodingZRLE; + } else if (choices[encodingIndex].getSelectedItem().equals("Zlib")) { + preferredEncoding = RfbProto.EncodingZlib; + enableCompressLevel = true; + } else if (choices[encodingIndex].getSelectedItem().equals("Tight")) { + preferredEncoding = RfbProto.EncodingTight; + enableCompressLevel = true; + } else if (choices[encodingIndex].getSelectedItem().equals("Auto")) { + preferredEncoding = -1; + } + + // Handle compression level setting. + + try { + compressLevel = + Integer.parseInt(choices[compressLevelIndex].getSelectedItem()); + } + catch (NumberFormatException e) { + compressLevel = -1; + } + if (compressLevel < 1 || compressLevel > 9) { + compressLevel = -1; + } + labels[compressLevelIndex].setEnabled(enableCompressLevel); + choices[compressLevelIndex].setEnabled(enableCompressLevel); + + // Handle JPEG quality setting. + + try { + jpegQuality = + Integer.parseInt(choices[jpegQualityIndex].getSelectedItem()); + } + catch (NumberFormatException e) { + jpegQuality = -1; + } + if (jpegQuality < 0 || jpegQuality > 9) { + jpegQuality = -1; + } + + // Request cursor shape updates if necessary. + + requestCursorUpdates = + !choices[cursorUpdatesIndex].getSelectedItem().equals("Disable"); + + if (requestCursorUpdates) { + ignoreCursorUpdates = + choices[cursorUpdatesIndex].getSelectedItem().equals("Ignore"); + } + + viewer.setEncodings(); + } + + // + // setColorFormat sets eightBitColors variable depending on the GUI + // setting, causing switches between 8-bit and 24-bit colors mode if + // necessary. + // + + void setColorFormat() { + + eightBitColors = + choices[eightBitColorsIndex].getSelectedItem().equals("Yes"); + + boolean enableJPEG = !eightBitColors; + + labels[jpegQualityIndex].setEnabled(enableJPEG); + choices[jpegQualityIndex].setEnabled(enableJPEG); + } + + // + // setOtherOptions looks at the "other" choices (ones that do not + // cause sending any protocol messages) and sets the boolean flags + // appropriately. + // + + void setOtherOptions() { + + reverseMouseButtons2And3 + = choices[mouseButtonIndex].getSelectedItem().equals("Reversed"); + + viewOnly + = choices[viewOnlyIndex].getSelectedItem().equals("Yes"); + if (viewer.vc != null) + viewer.vc.enableInput(!viewOnly); + + shareDesktop + = choices[shareDesktopIndex].getSelectedItem().equals("Yes"); + + String scaleString = choices[scaleCursorIndex].getSelectedItem(); + if (scaleString.endsWith("%")) + scaleString = scaleString.substring(0, scaleString.length() - 1); + try { + scaleCursor = Integer.parseInt(scaleString); + } + catch (NumberFormatException e) { + scaleCursor = 0; + } + if (scaleCursor < 10 || scaleCursor > 500) { + scaleCursor = 0; + } + if (requestCursorUpdates && !ignoreCursorUpdates && !viewOnly) { + labels[scaleCursorIndex].setEnabled(true); + choices[scaleCursorIndex].setEnabled(true); + } else { + labels[scaleCursorIndex].setEnabled(false); + choices[scaleCursorIndex].setEnabled(false); + } + if (viewer.vc != null) + viewer.vc.createSoftCursor(); // update cursor scaling + } + + + // + // Respond to actions on Choice controls + // + + public void itemStateChanged(ItemEvent evt) { + Object source = evt.getSource(); + + if (source == choices[encodingIndex] || + source == choices[compressLevelIndex] || + source == choices[jpegQualityIndex] || + source == choices[cursorUpdatesIndex] || + source == choices[useCopyRectIndex]) { + + setEncodings(); + + if (source == choices[cursorUpdatesIndex]) { + setOtherOptions(); // update scaleCursor state + } + + } else if (source == choices[eightBitColorsIndex]) { + + setColorFormat(); + + } else if (source == choices[mouseButtonIndex] || + source == choices[shareDesktopIndex] || + source == choices[viewOnlyIndex] || + source == choices[scaleCursorIndex]) { + + setOtherOptions(); + + } + } + + // + // Respond to button press + // + + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == closeButton) + setVisible(false); + } + + // + // Respond to window events + // + + public void windowClosing(WindowEvent evt) { + setVisible(false); + } + + public void windowActivated(WindowEvent evt) {} + public void windowDeactivated(WindowEvent evt) {} + public void windowOpened(WindowEvent evt) {} + public void windowClosed(WindowEvent evt) {} + public void windowIconified(WindowEvent evt) {} + public void windowDeiconified(WindowEvent evt) {} +} diff -r 000000000000 -r e04119c40b9b src/RecordingFrame.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/RecordingFrame.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,311 @@ +// +// Copyright (C) 2002 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// Recording frame. It allows to control recording RFB sessions into +// FBS (FrameBuffer Stream) files. +// + +import java.io.*; +import java.awt.*; +import java.awt.event.*; + +class RecordingFrame extends Frame + implements WindowListener, ActionListener { + + boolean recording; + + TextField fnameField; + Button browseButton; + + Label statusLabel; + + Button recordButton, nextButton, closeButton; + VncViewer viewer; + + // + // Check if current security manager allows to create a + // RecordingFrame object. + // + + public static boolean checkSecurity() { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + try { + security.checkPropertyAccess("user.dir"); + security.checkPropertyAccess("file.separator"); + // Work around (rare) checkPropertyAccess bug + System.getProperty("user.dir"); + } catch (SecurityException e) { + System.out.println("SecurityManager restricts session recording."); + return false; + } + } + return true; + } + + // + // Constructor. + // + + RecordingFrame(VncViewer v) { + super("TightVNC Session Recording"); + + viewer = v; + + // Determine initial filename for next saved session. + // FIXME: Check SecurityManager. + + String fname = nextNewFilename(System.getProperty("user.dir") + + System.getProperty("file.separator") + + "vncsession.fbs"); + + // Construct new panel with file name field and "Browse" button. + + Panel fnamePanel = new Panel(); + GridBagLayout fnameGridbag = new GridBagLayout(); + fnamePanel.setLayout(fnameGridbag); + + GridBagConstraints fnameConstraints = new GridBagConstraints(); + fnameConstraints.gridwidth = GridBagConstraints.RELATIVE; + fnameConstraints.fill = GridBagConstraints.BOTH; + fnameConstraints.weightx = 4.0; + + fnameField = new TextField(fname, 64); + fnameGridbag.setConstraints(fnameField, fnameConstraints); + fnamePanel.add(fnameField); + fnameField.addActionListener(this); + + fnameConstraints.gridwidth = GridBagConstraints.REMAINDER; + fnameConstraints.weightx = 1.0; + + browseButton = new Button("Browse"); + fnameGridbag.setConstraints(browseButton, fnameConstraints); + fnamePanel.add(browseButton); + browseButton.addActionListener(this); + + // Construct the frame. + + GridBagLayout gridbag = new GridBagLayout(); + setLayout(gridbag); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1.0; + gbc.insets = new Insets(10, 0, 0, 0); + + Label helpLabel = + new Label("File name to save next recorded session in:", Label.CENTER); + gridbag.setConstraints(helpLabel, gbc); + add(helpLabel); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weighty = 0.0; + gbc.insets = new Insets(0, 0, 0, 0); + + gridbag.setConstraints(fnamePanel, gbc); + add(fnamePanel); + + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1.0; + gbc.insets = new Insets(10, 0, 10, 0); + + statusLabel = new Label("", Label.CENTER); + gridbag.setConstraints(statusLabel, gbc); + add(statusLabel); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.weighty = 0.0; + gbc.gridwidth = 1; + gbc.insets = new Insets(0, 0, 0, 0); + + recordButton = new Button("Record"); + gridbag.setConstraints(recordButton, gbc); + add(recordButton); + recordButton.addActionListener(this); + + nextButton = new Button("Next file"); + gridbag.setConstraints(nextButton, gbc); + add(nextButton); + nextButton.addActionListener(this); + + closeButton = new Button("Close"); + gridbag.setConstraints(closeButton, gbc); + add(closeButton); + closeButton.addActionListener(this); + + // Set correct text, font and color for the statusLabel. + stopRecording(); + + pack(); + + addWindowListener(this); + } + + // + // If the given string ends with ".NNN" where NNN is a decimal + // number, increase this number by one. Otherwise, append ".001" + // to the given string. + // + + protected String nextFilename(String fname) { + int len = fname.length(); + int suffixPos = len; + int suffixNum = 1; + + if (len > 4 && fname.charAt(len - 4) == '.') { + try { + suffixNum = Integer.parseInt(fname.substring(len - 3, len)) + 1; + suffixPos = len - 4; + } catch (NumberFormatException e) { } + } + + char[] zeroes = {'0', '0', '0'}; + String suffix = String.valueOf(suffixNum); + if (suffix.length() < 3) { + suffix = new String(zeroes, 0, 3 - suffix.length()) + suffix; + } + + return fname.substring(0, suffixPos) + '.' + suffix; + } + + // + // Find next name of a file which does not exist yet. + // + + protected String nextNewFilename(String fname) { + String newName = fname; + File f; + try { + do { + newName = nextFilename(newName); + f = new File(newName); + } while (f.exists()); + } catch (SecurityException e) { } + + return newName; + } + + // + // Let the user choose a file name showing a FileDialog. + // + + protected boolean browseFile() { + File currentFile = new File(fnameField.getText()); + + FileDialog fd = + new FileDialog(this, "Save next session as...", FileDialog.SAVE); + fd.setDirectory(currentFile.getParent()); + fd.setVisible(true); + if (fd.getFile() != null) { + String newDir = fd.getDirectory(); + String sep = System.getProperty("file.separator"); + if (newDir.length() > 0) { + if (!sep.equals(newDir.substring(newDir.length() - sep.length()))) + newDir += sep; + } + String newFname = newDir + fd.getFile(); + if (newFname.equals(fnameField.getText())) { + fnameField.setText(newFname); + return true; + } + } + return false; + } + + // + // Start recording. + // + + public void startRecording() { + statusLabel.setText("Status: Recording..."); + statusLabel.setFont(new Font("Helvetica", Font.BOLD, 12)); + statusLabel.setForeground(Color.red); + recordButton.setLabel("Stop recording"); + + recording = true; + + viewer.setRecordingStatus(fnameField.getText()); + } + + // + // Stop recording. + // + + public void stopRecording() { + statusLabel.setText("Status: Not recording."); + statusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); + statusLabel.setForeground(Color.black); + recordButton.setLabel("Record"); + + recording = false; + + viewer.setRecordingStatus(null); + } + + // + // Close our window properly. + // + + public void windowClosing(WindowEvent evt) { + setVisible(false); + } + + // + // Ignore window events we're not interested in. + // + + public void windowActivated(WindowEvent evt) {} + public void windowDeactivated (WindowEvent evt) {} + public void windowOpened(WindowEvent evt) {} + public void windowClosed(WindowEvent evt) {} + public void windowIconified(WindowEvent evt) {} + public void windowDeiconified(WindowEvent evt) {} + + + // + // Respond to button presses + // + + public void actionPerformed(ActionEvent evt) { + if (evt.getSource() == browseButton) { + if (browseFile() && recording) + startRecording(); + + } else if (evt.getSource() == recordButton) { + if (!recording) { + startRecording(); + } else { + stopRecording(); + fnameField.setText(nextNewFilename(fnameField.getText())); + } + + } else if (evt.getSource() == nextButton) { + fnameField.setText(nextNewFilename(fnameField.getText())); + if (recording) + startRecording(); + + } else if (evt.getSource() == closeButton) { + setVisible(false); + + } + } +} diff -r 000000000000 -r e04119c40b9b src/ReloginPanel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ReloginPanel.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,65 @@ +// +// Copyright (C) 2002 Cendio Systems. All Rights Reserved. +// Copyright (C) 2002 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// ReloginPanel class implements panel with a button for logging in again, +// after fatal errors or disconnect +// + + +import java.awt.*; +import java.awt.event.*; +import java.applet.*; + +// +// The panel which implements the Relogin button +// + +class ReloginPanel extends Panel implements ActionListener { + Button reloginButton; + Button closeButton; + VncViewer viewer; + + // + // Constructor. + // + public ReloginPanel(VncViewer v) { + viewer = v; + setLayout(new FlowLayout(FlowLayout.CENTER)); + reloginButton = new Button("Login again"); + add(reloginButton); + reloginButton.addActionListener(this); + if (viewer.inSeparateFrame) { + closeButton = new Button("Close window"); + add(closeButton); + closeButton.addActionListener(this); + } + } + + // + // This method is called when a button is pressed. + // + public synchronized void actionPerformed(ActionEvent evt) { + if (viewer.inSeparateFrame) + viewer.vncFrame.dispose(); + if (evt.getSource() == reloginButton) + viewer.getAppletContext().showDocument(viewer.getDocumentBase()); + } +} diff -r 000000000000 -r e04119c40b9b src/RfbProto.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/RfbProto.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,1392 @@ +// +// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 2001-2006 Constantin Kaplinsky. All Rights Reserved. +// Copyright (C) 2000 Tridia Corporation. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// RfbProto.java +// + +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import java.net.Socket; +import java.util.zip.*; + +class RfbProto { + + final static String + versionMsg_3_3 = "RFB 003.003\n", + versionMsg_3_7 = "RFB 003.007\n", + versionMsg_3_8 = "RFB 003.008\n"; + + // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC + final static String + StandardVendor = "STDV", + TridiaVncVendor = "TRDV", + TightVncVendor = "TGHT"; + + // Security types + final static int + SecTypeInvalid = 0, + SecTypeNone = 1, + SecTypeVncAuth = 2, + SecTypeTight = 16; + + // Supported tunneling types + final static int + NoTunneling = 0; + final static String + SigNoTunneling = "NOTUNNEL"; + + // Supported authentication types + final static int + AuthNone = 1, + AuthVNC = 2, + AuthUnixLogin = 129; + final static String + SigAuthNone = "NOAUTH__", + SigAuthVNC = "VNCAUTH_", + SigAuthUnixLogin = "ULGNAUTH"; + + // VNC authentication results + final static int + VncAuthOK = 0, + VncAuthFailed = 1, + VncAuthTooMany = 2; + + // Standard server-to-client messages + final static int + FramebufferUpdate = 0, + SetColourMapEntries = 1, + Bell = 2, + ServerCutText = 3; + + // Non-standard server-to-client messages + final static int + EndOfContinuousUpdates = 150; + final static String + SigEndOfContinuousUpdates = "CUS_EOCU"; + + // Standard client-to-server messages + final static int + SetPixelFormat = 0, + FixColourMapEntries = 1, + SetEncodings = 2, + FramebufferUpdateRequest = 3, + KeyboardEvent = 4, + PointerEvent = 5, + ClientCutText = 6; + + // Non-standard client-to-server messages + final static int + EnableContinuousUpdates = 150; + final static String + SigEnableContinuousUpdates = "CUC_ENCU"; + + // Supported encodings and pseudo-encodings + final static int + EncodingRaw = 0, + EncodingCopyRect = 1, + EncodingRRE = 2, + EncodingCoRRE = 4, + EncodingHextile = 5, + EncodingZlib = 6, + EncodingTight = 7, + EncodingZRLE = 16, + EncodingCompressLevel0 = 0xFFFFFF00, + EncodingQualityLevel0 = 0xFFFFFFE0, + EncodingXCursor = 0xFFFFFF10, + EncodingRichCursor = 0xFFFFFF11, + EncodingPointerPos = 0xFFFFFF18, + EncodingLastRect = 0xFFFFFF20, + EncodingNewFBSize = 0xFFFFFF21; + final static String + SigEncodingRaw = "RAW_____", + SigEncodingCopyRect = "COPYRECT", + SigEncodingRRE = "RRE_____", + SigEncodingCoRRE = "CORRE___", + SigEncodingHextile = "HEXTILE_", + SigEncodingZlib = "ZLIB____", + SigEncodingTight = "TIGHT___", + SigEncodingZRLE = "ZRLE____", + SigEncodingCompressLevel0 = "COMPRLVL", + SigEncodingQualityLevel0 = "JPEGQLVL", + SigEncodingXCursor = "X11CURSR", + SigEncodingRichCursor = "RCHCURSR", + SigEncodingPointerPos = "POINTPOS", + SigEncodingLastRect = "LASTRECT", + SigEncodingNewFBSize = "NEWFBSIZ"; + + final static int MaxNormalEncoding = 255; + + // Contstants used in the Hextile decoder + final static int + HextileRaw = 1, + HextileBackgroundSpecified = 2, + HextileForegroundSpecified = 4, + HextileAnySubrects = 8, + HextileSubrectsColoured = 16; + + // Contstants used in the Tight decoder + final static int TightMinToCompress = 12; + final static int + TightExplicitFilter = 0x04, + TightFill = 0x08, + TightJpeg = 0x09, + TightMaxSubencoding = 0x09, + TightFilterCopy = 0x00, + TightFilterPalette = 0x01, + TightFilterGradient = 0x02; + + + String host; + int port; + Socket sock; + OutputStream os; + SessionRecorder rec; + boolean inNormalProtocol = false; + VncViewer viewer; + + // Input stream is declared private to make sure it can be accessed + // only via RfbProto methods. We have to do this because we want to + // count how many bytes were read. + private DataInputStream is; + private long numBytesRead = 0; + public long getNumBytesRead() { return numBytesRead; } + + boolean TEST=false; + Socket cliSock; + + + // Java on UNIX does not call keyPressed() on some keys, for example + // swedish keys To prevent our workaround to produce duplicate + // keypresses on JVMs that actually works, keep track of if + // keyPressed() for a "broken" key was called or not. + boolean brokenKeyPressed = false; + + // This will be set to true on the first framebuffer update + // containing Zlib-, ZRLE- or Tight-encoded data. + boolean wereZlibUpdates = false; + + // This will be set to false if the startSession() was called after + // we have received at least one Zlib-, ZRLE- or Tight-encoded + // framebuffer update. + boolean recordFromBeginning = true; + + // This fields are needed to show warnings about inefficiently saved + // sessions only once per each saved session file. + boolean zlibWarningShown; + boolean tightWarningShown; + + // Before starting to record each saved session, we set this field + // to 0, and increment on each framebuffer update. We don't flush + // the SessionRecorder data into the file before the second update. + // This allows us to write initial framebuffer update with zero + // timestamp, to let the player show initial desktop before + // playback. + int numUpdatesInSession; + + // Measuring network throughput. + boolean timing; + long timeWaitedIn100us; + long timedKbits; + + // Protocol version and TightVNC-specific protocol options. + int serverMajor, serverMinor; + int clientMajor, clientMinor; + boolean protocolTightVNC; + CapsContainer tunnelCaps, authCaps; + CapsContainer serverMsgCaps, clientMsgCaps; + CapsContainer encodingCaps; + + // If true, informs that the RFB socket was closed. + private boolean closed; + + // + // Constructor. Make TCP connection to RFB server. + // + + RfbProto(String h, int p, VncViewer v) throws IOException { + viewer = v; + host = h; + port = p; + + if (viewer.socketFactory == null) { + sock = new Socket(host, port); + if(TEST){ + cliSock = new Socket("localhost",5550); + } + + } else { + try { + Class factoryClass = Class.forName(viewer.socketFactory); + SocketFactory factory = (SocketFactory)factoryClass.newInstance(); + if (viewer.inAnApplet) + sock = factory.createSocket(host, port, viewer); + else + sock = factory.createSocket(host, port, viewer.mainArgs); + } catch(Exception e) { + e.printStackTrace(); + throw new IOException(e.getMessage()); + } + } + is = new DataInputStream(new BufferedInputStream(sock.getInputStream(), + 16384)); + os = sock.getOutputStream(); + + timing = false; + timeWaitedIn100us = 5; + timedKbits = 0; + } + + + synchronized void close() { + try { + sock.close(); + closed = true; + System.out.println("RFB socket closed"); + if (rec != null) { + rec.close(); + rec = null; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + synchronized boolean closed() { + return closed; + } + + // + // Read server's protocol version message + // + + void readVersionMsg() throws Exception { + + byte[] b = new byte[12]; + + readFully(b); + + if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ') + || (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9') + || (b[6] < '0') || (b[6] > '9') || (b[7] != '.') + || (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9') + || (b[10] < '0') || (b[10] > '9') || (b[11] != '\n')) + { + throw new Exception("Host " + host + " port " + port + + " is not an RFB server"); + } + + serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0'); + serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0'); + + if (serverMajor < 3) { + throw new Exception("RFB server does not support protocol version 3"); + } + } + + + // + // Write our protocol version message + // + + void writeVersionMsg() throws IOException { + clientMajor = 3; + if (serverMajor > 3 || serverMinor >= 8) { + clientMinor = 8; + os.write(versionMsg_3_8.getBytes()); + } else if (serverMinor >= 7) { + clientMinor = 7; + os.write(versionMsg_3_7.getBytes()); + } else { + clientMinor = 3; + os.write(versionMsg_3_3.getBytes()); + } + protocolTightVNC = false; + initCapabilities(); + } + + + // + // Negotiate the authentication scheme. + // + + int negotiateSecurity() throws Exception { + return (clientMinor >= 7) ? + selectSecurityType() : readSecurityType(); + } + + // + // Read security type from the server (protocol version 3.3). + // + + int readSecurityType() throws Exception { + int secType = readU32(); + + switch (secType) { + case SecTypeInvalid: + readConnFailedReason(); + return SecTypeInvalid; // should never be executed + case SecTypeNone: + case SecTypeVncAuth: + return secType; + default: + throw new Exception("Unknown security type from RFB server: " + secType); + } + } + + // + // Select security type from the server's list (protocol versions 3.7/3.8). + // + + int selectSecurityType() throws Exception { + int secType = SecTypeInvalid; + + // Read the list of secutiry types. + int nSecTypes = readU8(); + if (nSecTypes == 0) { + readConnFailedReason(); + return SecTypeInvalid; // should never be executed + } + byte[] secTypes = new byte[nSecTypes]; + readFully(secTypes); + + // Find out if the server supports TightVNC protocol extensions + for (int i = 0; i < nSecTypes; i++) { + if (secTypes[i] == SecTypeTight) { + protocolTightVNC = true; + os.write(SecTypeTight); + return SecTypeTight; + } + } + + // Find first supported security type. + for (int i = 0; i < nSecTypes; i++) { + if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth) { + secType = secTypes[i]; + break; + } + } + + if (secType == SecTypeInvalid) { + throw new Exception("Server did not offer supported security type"); + } else { + os.write(secType); + } + + return secType; + } + + // + // Perform "no authentication". + // + + void authenticateNone() throws Exception { + if (clientMinor >= 8) + readSecurityResult("No authentication"); + } + + // + // Perform standard VNC Authentication. + // + + void authenticateVNC(String pw) throws Exception { + byte[] challenge = new byte[16]; + readFully(challenge); + + if (pw.length() > 8) + pw = pw.substring(0, 8); // Truncate to 8 chars + + // Truncate password on the first zero byte. + int firstZero = pw.indexOf(0); + if (firstZero != -1) + pw = pw.substring(0, firstZero); + + byte[] key = {0, 0, 0, 0, 0, 0, 0, 0}; + System.arraycopy(pw.getBytes(), 0, key, 0, pw.length()); + + DesCipher des = new DesCipher(key); + + des.encrypt(challenge, 0, challenge, 0); + des.encrypt(challenge, 8, challenge, 8); + + os.write(challenge); + + readSecurityResult("VNC authentication"); + } + + // + // Read security result. + // Throws an exception on authentication failure. + // + + void readSecurityResult(String authType) throws Exception { + int securityResult = readU32(); + + switch (securityResult) { + case VncAuthOK: + System.out.println(authType + ": success"); + break; + case VncAuthFailed: + if (clientMinor >= 8) + readConnFailedReason(); + throw new Exception(authType + ": failed"); + case VncAuthTooMany: + throw new Exception(authType + ": failed, too many tries"); + default: + throw new Exception(authType + ": unknown result " + securityResult); + } + } + + // + // Read the string describing the reason for a connection failure, + // and throw an exception. + // + + void readConnFailedReason() throws Exception { + int reasonLen = readU32(); + byte[] reason = new byte[reasonLen]; + readFully(reason); + throw new Exception(new String(reason)); + } + + // + // Initialize capability lists (TightVNC protocol extensions). + // + + void initCapabilities() { + tunnelCaps = new CapsContainer(); + authCaps = new CapsContainer(); + serverMsgCaps = new CapsContainer(); + clientMsgCaps = new CapsContainer(); + encodingCaps = new CapsContainer(); + + // Supported authentication methods + authCaps.add(AuthNone, StandardVendor, SigAuthNone, + "No authentication"); + authCaps.add(AuthVNC, StandardVendor, SigAuthVNC, + "Standard VNC password authentication"); + + // Supported non-standard server-to-client messages + // [NONE] + + // Supported non-standard client-to-server messages + // [NONE] + + // Supported encoding types + encodingCaps.add(EncodingCopyRect, StandardVendor, + SigEncodingCopyRect, "Standard CopyRect encoding"); + encodingCaps.add(EncodingRRE, StandardVendor, + SigEncodingRRE, "Standard RRE encoding"); + encodingCaps.add(EncodingCoRRE, StandardVendor, + SigEncodingCoRRE, "Standard CoRRE encoding"); + encodingCaps.add(EncodingHextile, StandardVendor, + SigEncodingHextile, "Standard Hextile encoding"); + encodingCaps.add(EncodingZRLE, StandardVendor, + SigEncodingZRLE, "Standard ZRLE encoding"); + encodingCaps.add(EncodingZlib, TridiaVncVendor, + SigEncodingZlib, "Zlib encoding"); + encodingCaps.add(EncodingTight, TightVncVendor, + SigEncodingTight, "Tight encoding"); + + // Supported pseudo-encoding types + encodingCaps.add(EncodingCompressLevel0, TightVncVendor, + SigEncodingCompressLevel0, "Compression level"); + encodingCaps.add(EncodingQualityLevel0, TightVncVendor, + SigEncodingQualityLevel0, "JPEG quality level"); + encodingCaps.add(EncodingXCursor, TightVncVendor, + SigEncodingXCursor, "X-style cursor shape update"); + encodingCaps.add(EncodingRichCursor, TightVncVendor, + SigEncodingRichCursor, "Rich-color cursor shape update"); + encodingCaps.add(EncodingPointerPos, TightVncVendor, + SigEncodingPointerPos, "Pointer position update"); + encodingCaps.add(EncodingLastRect, TightVncVendor, + SigEncodingLastRect, "LastRect protocol extension"); + encodingCaps.add(EncodingNewFBSize, TightVncVendor, + SigEncodingNewFBSize, "Framebuffer size change"); + } + + // + // Setup tunneling (TightVNC protocol extensions) + // + + void setupTunneling() throws IOException { + int nTunnelTypes = readU32(); + if (nTunnelTypes != 0) { + readCapabilityList(tunnelCaps, nTunnelTypes); + + // We don't support tunneling yet. + writeInt(NoTunneling); + } + } + + // + // Negotiate authentication scheme (TightVNC protocol extensions) + // + + int negotiateAuthenticationTight() throws Exception { + int nAuthTypes = readU32(); + if (nAuthTypes == 0) + return AuthNone; + + readCapabilityList(authCaps, nAuthTypes); + for (int i = 0; i < authCaps.numEnabled(); i++) { + int authType = authCaps.getByOrder(i); + if (authType == AuthNone || authType == AuthVNC) { + writeInt(authType); + return authType; + } + } + throw new Exception("No suitable authentication scheme found"); + } + + // + // Read a capability list (TightVNC protocol extensions) + // + + void readCapabilityList(CapsContainer caps, int count) throws IOException { + int code; + byte[] vendor = new byte[4]; + byte[] name = new byte[8]; + for (int i = 0; i < count; i++) { + code = readU32(); + readFully(vendor); + readFully(name); + caps.enable(new CapabilityInfo(code, vendor, name)); + } + } + + // + // Write a 32-bit integer into the output stream. + // + + void writeInt(int value) throws IOException { + byte[] b = new byte[4]; + b[0] = (byte) ((value >> 24) & 0xff); + b[1] = (byte) ((value >> 16) & 0xff); + b[2] = (byte) ((value >> 8) & 0xff); + b[3] = (byte) (value & 0xff); + os.write(b); + } + + // + // Write the client initialisation message + // + + void writeClientInit() throws IOException { + if (viewer.options.shareDesktop) { + os.write(1); + } else { + os.write(0); + } + viewer.options.disableShareDesktop(); + } + + + // + // Read the server initialisation message + // + + String desktopName; + int framebufferWidth, framebufferHeight; + int bitsPerPixel, depth; + boolean bigEndian, trueColour; + int redMax, greenMax, blueMax, redShift, greenShift, blueShift; + + void readServerInit() throws IOException { + framebufferWidth = readU16(); + framebufferHeight = readU16(); + bitsPerPixel = readU8(); + depth = readU8(); + bigEndian = (readU8() != 0); + trueColour = (readU8() != 0); + redMax = readU16(); + greenMax = readU16(); + blueMax = readU16(); + redShift = readU8(); + greenShift = readU8(); + blueShift = readU8(); + byte[] pad = new byte[3]; + readFully(pad); + int nameLength = readU32(); + byte[] name = new byte[nameLength]; + readFully(name); + desktopName = new String(name); + + // Read interaction capabilities (TightVNC protocol extensions) + if (protocolTightVNC) { + int nServerMessageTypes = readU16(); + int nClientMessageTypes = readU16(); + int nEncodingTypes = readU16(); + readU16(); + readCapabilityList(serverMsgCaps, nServerMessageTypes); + readCapabilityList(clientMsgCaps, nClientMessageTypes); + readCapabilityList(encodingCaps, nEncodingTypes); + } + + inNormalProtocol = true; + } + + + // + // Create session file and write initial protocol messages into it. + // + + void startSession(String fname) throws IOException { + rec = new SessionRecorder(fname); + rec.writeHeader(); + rec.write(versionMsg_3_3.getBytes()); + rec.writeIntBE(SecTypeNone); + rec.writeShortBE(framebufferWidth); + rec.writeShortBE(framebufferHeight); + byte[] fbsServerInitMsg = { + 32, 24, 0, 1, 0, + (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF, + 16, 8, 0, 0, 0, 0 + }; + rec.write(fbsServerInitMsg); + rec.writeIntBE(desktopName.length()); + rec.write(desktopName.getBytes()); + numUpdatesInSession = 0; + + // FIXME: If there were e.g. ZRLE updates only, that should not + // affect recording of Zlib and Tight updates. So, actually + // we should maintain separate flags for Zlib, ZRLE and + // Tight, instead of one ``wereZlibUpdates'' variable. + // + if (wereZlibUpdates) + recordFromBeginning = false; + + zlibWarningShown = false; + tightWarningShown = false; + } + + // + // Close session file. + // + + void closeSession() throws IOException { + if (rec != null) { + rec.close(); + rec = null; + } + } + + + // + // Set new framebuffer size + // + + void setFramebufferSize(int width, int height) { + framebufferWidth = width; + framebufferHeight = height; + } + + + // + // Read the server message type + // + + int readServerMessageType() throws IOException { + int msgType = readU8(); + + // If the session is being recorded: + if (rec != null) { + if (msgType == Bell) { // Save Bell messages in session files. + rec.writeByte(msgType); + if (numUpdatesInSession > 0) + rec.flush(); + } + } + + return msgType; + } + + + // + // Read a FramebufferUpdate message + // + + int updateNRects; + + void readFramebufferUpdate() throws IOException { + skipBytes(1); + updateNRects = readU16(); +// System.out.println(updateNRects); + + // If the session is being recorded: + if (rec != null) { + rec.writeByte(FramebufferUpdate); + rec.writeByte(0); + rec.writeShortBE(updateNRects); + } + + numUpdatesInSession++; + } + + // Read a FramebufferUpdate rectangle header + + int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding; + + void readFramebufferUpdateRectHdr() throws Exception { + updateRectX = readU16(); + updateRectY = readU16(); + updateRectW = readU16(); + updateRectH = readU16(); + updateRectEncoding = readU32(); +// System.out.println("readU16&32"); + + if (updateRectEncoding == EncodingZlib || + updateRectEncoding == EncodingZRLE || + updateRectEncoding == EncodingTight) + wereZlibUpdates = true; + + // If the session is being recorded: + if (rec != null) { + if (numUpdatesInSession > 1) + rec.flush(); // Flush the output on each rectangle. + rec.writeShortBE(updateRectX); + rec.writeShortBE(updateRectY); + rec.writeShortBE(updateRectW); + rec.writeShortBE(updateRectH); + if (updateRectEncoding == EncodingZlib && !recordFromBeginning) { + // Here we cannot write Zlib-encoded rectangles because the + // decoder won't be able to reproduce zlib stream state. + if (!zlibWarningShown) { + System.out.println("Warning: Raw encoding will be used " + + "instead of Zlib in recorded session."); + zlibWarningShown = true; + } + rec.writeIntBE(EncodingRaw); + } else { + rec.writeIntBE(updateRectEncoding); + if (updateRectEncoding == EncodingTight && !recordFromBeginning && + !tightWarningShown) { + System.out.println("Warning: Re-compressing Tight-encoded " + + "updates for session recording."); + tightWarningShown = true; + } + } + } + + if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding) + return; + + if (updateRectX + updateRectW > framebufferWidth || + updateRectY + updateRectH > framebufferHeight) { + throw new Exception("Framebuffer update rectangle too large: " + + updateRectW + "x" + updateRectH + " at (" + + updateRectX + "," + updateRectY + ")"); + } + } + + // Read CopyRect source X and Y. + + int copyRectSrcX, copyRectSrcY; + + void readCopyRect() throws IOException { + copyRectSrcX = readU16(); + copyRectSrcY = readU16(); + + // If the session is being recorded: + if (rec != null) { + rec.writeShortBE(copyRectSrcX); + rec.writeShortBE(copyRectSrcY); + } + } + + + // + // Read a ServerCutText message + // + + String readServerCutText() throws IOException { + skipBytes(3); + int len = readU32(); + byte[] text = new byte[len]; + readFully(text); + return new String(text); + } + + + // + // Read an integer in compact representation (1..3 bytes). + // Such format is used as a part of the Tight encoding. + // Also, this method records data if session recording is active and + // the viewer's recordFromBeginning variable is set to true. + // + + int readCompactLen() throws IOException { + int[] portion = new int[3]; + portion[0] = readU8(); + int byteCount = 1; + int len = portion[0] & 0x7F; + if ((portion[0] & 0x80) != 0) { + portion[1] = readU8(); + byteCount++; + len |= (portion[1] & 0x7F) << 7; + if ((portion[1] & 0x80) != 0) { + portion[2] = readU8(); + byteCount++; + len |= (portion[2] & 0xFF) << 14; + } + } + + if (rec != null && recordFromBeginning) + for (int i = 0; i < byteCount; i++) + rec.writeByte(portion[i]); + + return len; + } + + + // + // Write a FramebufferUpdateRequest message + // + + void writeFramebufferUpdateRequest(int x, int y, int w, int h, + boolean incremental) + throws IOException + { + byte[] b = new byte[10]; + + b[0] = (byte) FramebufferUpdateRequest; + b[1] = (byte) (incremental ? 1 : 0); + b[2] = (byte) ((x >> 8) & 0xff); + b[3] = (byte) (x & 0xff); + b[4] = (byte) ((y >> 8) & 0xff); + b[5] = (byte) (y & 0xff); + b[6] = (byte) ((w >> 8) & 0xff); + b[7] = (byte) (w & 0xff); + b[8] = (byte) ((h >> 8) & 0xff); + b[9] = (byte) (h & 0xff); + + os.write(b); + } + + + // + // Write a SetPixelFormat message + // + + void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian, + boolean trueColour, + int redMax, int greenMax, int blueMax, + int redShift, int greenShift, int blueShift) + throws IOException + { + byte[] b = new byte[20]; + + b[0] = (byte) SetPixelFormat; + b[4] = (byte) bitsPerPixel; + b[5] = (byte) depth; + b[6] = (byte) (bigEndian ? 1 : 0); + b[7] = (byte) (trueColour ? 1 : 0); + b[8] = (byte) ((redMax >> 8) & 0xff); + b[9] = (byte) (redMax & 0xff); + b[10] = (byte) ((greenMax >> 8) & 0xff); + b[11] = (byte) (greenMax & 0xff); + b[12] = (byte) ((blueMax >> 8) & 0xff); + b[13] = (byte) (blueMax & 0xff); + b[14] = (byte) redShift; + b[15] = (byte) greenShift; + b[16] = (byte) blueShift; + + os.write(b); + } + + + // + // Write a FixColourMapEntries message. The values in the red, green and + // blue arrays are from 0 to 65535. + // + + void writeFixColourMapEntries(int firstColour, int nColours, + int[] red, int[] green, int[] blue) + throws IOException + { + byte[] b = new byte[6 + nColours * 6]; + + b[0] = (byte) FixColourMapEntries; + b[2] = (byte) ((firstColour >> 8) & 0xff); + b[3] = (byte) (firstColour & 0xff); + b[4] = (byte) ((nColours >> 8) & 0xff); + b[5] = (byte) (nColours & 0xff); + + for (int i = 0; i < nColours; i++) { + b[6 + i * 6] = (byte) ((red[i] >> 8) & 0xff); + b[6 + i * 6 + 1] = (byte) (red[i] & 0xff); + b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff); + b[6 + i * 6 + 3] = (byte) (green[i] & 0xff); + b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff); + b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff); + } + + os.write(b); + } + + + // + // Write a SetEncodings message + // + + void writeSetEncodings(int[] encs, int len) throws IOException { + byte[] b = new byte[4 + 4 * len]; + + b[0] = (byte) SetEncodings; + b[2] = (byte) ((len >> 8) & 0xff); + b[3] = (byte) (len & 0xff); + + for (int i = 0; i < len; i++) { + b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff); + b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff); + b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff); + b[7 + 4 * i] = (byte) (encs[i] & 0xff); + } + + os.write(b); + } + + + // + // Write a ClientCutText message + // + + void writeClientCutText(String text) throws IOException { + byte[] b = new byte[8 + text.length()]; + + b[0] = (byte) ClientCutText; + b[4] = (byte) ((text.length() >> 24) & 0xff); + b[5] = (byte) ((text.length() >> 16) & 0xff); + b[6] = (byte) ((text.length() >> 8) & 0xff); + b[7] = (byte) (text.length() & 0xff); + + System.arraycopy(text.getBytes(), 0, b, 8, text.length()); + + os.write(b); + } + + + // + // A buffer for putting pointer and keyboard events before being sent. This + // is to ensure that multiple RFB events generated from a single Java Event + // will all be sent in a single network packet. The maximum possible + // length is 4 modifier down events, a single key event followed by 4 + // modifier up events i.e. 9 key events or 72 bytes. + // + + byte[] eventBuf = new byte[72]; + int eventBufLen; + + + // Useful shortcuts for modifier masks. + + final static int CTRL_MASK = InputEvent.CTRL_MASK; + final static int SHIFT_MASK = InputEvent.SHIFT_MASK; + final static int META_MASK = InputEvent.META_MASK; + final static int ALT_MASK = InputEvent.ALT_MASK; + + + // + // Write a pointer event message. We may need to send modifier key events + // around it to set the correct modifier state. + // + + int pointerMask = 0; + + void writePointerEvent(MouseEvent evt) throws IOException { + int modifiers = evt.getModifiers(); + + int mask2 = 2; + int mask3 = 4; + if (viewer.options.reverseMouseButtons2And3) { + mask2 = 4; + mask3 = 2; + } + + // Note: For some reason, AWT does not set BUTTON1_MASK on left + // button presses. Here we think that it was the left button if + // modifiers do not include BUTTON2_MASK or BUTTON3_MASK. + + if (evt.getID() == MouseEvent.MOUSE_PRESSED) { + if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + pointerMask = mask2; + modifiers &= ~ALT_MASK; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + pointerMask = mask3; + modifiers &= ~META_MASK; + } else { + pointerMask = 1; + } + } else if (evt.getID() == MouseEvent.MOUSE_RELEASED) { + pointerMask = 0; + if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { + modifiers &= ~ALT_MASK; + } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) { + modifiers &= ~META_MASK; + } + } + + eventBufLen = 0; + writeModifierKeyEvents(modifiers); + + int x = evt.getX(); + int y = evt.getY(); + + if (x < 0) x = 0; + if (y < 0) y = 0; + + eventBuf[eventBufLen++] = (byte) PointerEvent; + eventBuf[eventBufLen++] = (byte) pointerMask; + eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff); + eventBuf[eventBufLen++] = (byte) (x & 0xff); + eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff); + eventBuf[eventBufLen++] = (byte) (y & 0xff); + + // + // Always release all modifiers after an "up" event + // + + if (pointerMask == 0) { + writeModifierKeyEvents(0); + } + + os.write(eventBuf, 0, eventBufLen); + } + + + // + // Write a key event message. We may need to send modifier key events + // around it to set the correct modifier state. Also we need to translate + // from the Java key values to the X keysym values used by the RFB protocol. + // + + void writeKeyEvent(KeyEvent evt) throws IOException { + + int keyChar = evt.getKeyChar(); + + // + // Ignore event if only modifiers were pressed. + // + + // Some JVMs return 0 instead of CHAR_UNDEFINED in getKeyChar(). + if (keyChar == 0) + keyChar = KeyEvent.CHAR_UNDEFINED; + + if (keyChar == KeyEvent.CHAR_UNDEFINED) { + int code = evt.getKeyCode(); + if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT || + code == KeyEvent.VK_META || code == KeyEvent.VK_ALT) + return; + } + + // + // Key press or key release? + // + + boolean down = (evt.getID() == KeyEvent.KEY_PRESSED); + + int key; + if (evt.isActionKey()) { + + // + // An action key should be one of the following. + // If not then just ignore the event. + // + + switch(evt.getKeyCode()) { + case KeyEvent.VK_HOME: key = 0xff50; break; + case KeyEvent.VK_LEFT: key = 0xff51; break; + case KeyEvent.VK_UP: key = 0xff52; break; + case KeyEvent.VK_RIGHT: key = 0xff53; break; + case KeyEvent.VK_DOWN: key = 0xff54; break; + case KeyEvent.VK_PAGE_UP: key = 0xff55; break; + case KeyEvent.VK_PAGE_DOWN: key = 0xff56; break; + case KeyEvent.VK_END: key = 0xff57; break; + case KeyEvent.VK_INSERT: key = 0xff63; break; + case KeyEvent.VK_F1: key = 0xffbe; break; + case KeyEvent.VK_F2: key = 0xffbf; break; + case KeyEvent.VK_F3: key = 0xffc0; break; + case KeyEvent.VK_F4: key = 0xffc1; break; + case KeyEvent.VK_F5: key = 0xffc2; break; + case KeyEvent.VK_F6: key = 0xffc3; break; + case KeyEvent.VK_F7: key = 0xffc4; break; + case KeyEvent.VK_F8: key = 0xffc5; break; + case KeyEvent.VK_F9: key = 0xffc6; break; + case KeyEvent.VK_F10: key = 0xffc7; break; + case KeyEvent.VK_F11: key = 0xffc8; break; + case KeyEvent.VK_F12: key = 0xffc9; break; + default: + return; + } + + } else { + + // + // A "normal" key press. Ordinary ASCII characters go straight through. + // For CTRL-, CTRL is sent separately so just send . + // Backspace, tab, return, escape and delete have special keysyms. + // Anything else we ignore. + // + + key = keyChar; + + if (key < 0x20) { + if (evt.isControlDown()) { + key += 0x60; + } else { + switch(key) { + case KeyEvent.VK_BACK_SPACE: key = 0xff08; break; + case KeyEvent.VK_TAB: key = 0xff09; break; + case KeyEvent.VK_ENTER: key = 0xff0d; break; + case KeyEvent.VK_ESCAPE: key = 0xff1b; break; + } + } + } else if (key == 0x7f) { + // Delete + key = 0xffff; + } else if (key > 0xff) { + // JDK1.1 on X incorrectly passes some keysyms straight through, + // so we do too. JDK1.1.4 seems to have fixed this. + // The keysyms passed are 0xff00 .. XK_BackSpace .. XK_Delete + // Also, we pass through foreign currency keysyms (0x20a0..0x20af). + if ((key < 0xff00 || key > 0xffff) && + !(key >= 0x20a0 && key <= 0x20af)) + return; + } + } + + // Fake keyPresses for keys that only generates keyRelease events + if ((key == 0xe5) || (key == 0xc5) || // XK_aring / XK_Aring + (key == 0xe4) || (key == 0xc4) || // XK_adiaeresis / XK_Adiaeresis + (key == 0xf6) || (key == 0xd6) || // XK_odiaeresis / XK_Odiaeresis + (key == 0xa7) || (key == 0xbd) || // XK_section / XK_onehalf + (key == 0xa3)) { // XK_sterling + // Make sure we do not send keypress events twice on platforms + // with correct JVMs (those that actually report KeyPress for all + // keys) + if (down) + brokenKeyPressed = true; + + if (!down && !brokenKeyPressed) { + // We've got a release event for this key, but haven't received + // a press. Fake it. + eventBufLen = 0; + writeModifierKeyEvents(evt.getModifiers()); + writeKeyEvent(key, true); + os.write(eventBuf, 0, eventBufLen); + } + + if (!down) + brokenKeyPressed = false; + } + + eventBufLen = 0; + writeModifierKeyEvents(evt.getModifiers()); + writeKeyEvent(key, down); + + // Always release all modifiers after an "up" event + if (!down) + writeModifierKeyEvents(0); + + os.write(eventBuf, 0, eventBufLen); + } + + + // + // Add a raw key event with the given X keysym to eventBuf. + // + + void writeKeyEvent(int keysym, boolean down) { + eventBuf[eventBufLen++] = (byte) KeyboardEvent; + eventBuf[eventBufLen++] = (byte) (down ? 1 : 0); + eventBuf[eventBufLen++] = (byte) 0; + eventBuf[eventBufLen++] = (byte) 0; + eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff); + eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff); + eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff); + eventBuf[eventBufLen++] = (byte) (keysym & 0xff); + } + + + // + // Write key events to set the correct modifier state. + // + + int oldModifiers = 0; + + void writeModifierKeyEvents(int newModifiers) { + if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK)) + writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0); + + if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK)) + writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0); + + if ((newModifiers & META_MASK) != (oldModifiers & META_MASK)) + writeKeyEvent(0xffe7, (newModifiers & META_MASK) != 0); + + if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK)) + writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0); + + oldModifiers = newModifiers; + } + + // + // Compress and write the data into the recorded session file. This + // method assumes the recording is on (rec != null). + // + + void recordCompressedData(byte[] data, int off, int len) throws IOException { + Deflater deflater = new Deflater(); + deflater.setInput(data, off, len); + int bufSize = len + len / 100 + 12; + byte[] buf = new byte[bufSize]; + deflater.finish(); + int compressedSize = deflater.deflate(buf); + recordCompactLen(compressedSize); + rec.write(buf, 0, compressedSize); + } + + void recordCompressedData(byte[] data) throws IOException { + recordCompressedData(data, 0, data.length); + } + + // + // Write an integer in compact representation (1..3 bytes) into the + // recorded session file. This method assumes the recording is on + // (rec != null). + // + + void recordCompactLen(int len) throws IOException { + byte[] buf = new byte[3]; + int bytes = 0; + buf[bytes++] = (byte)(len & 0x7F); + if (len > 0x7F) { + buf[bytes-1] |= 0x80; + buf[bytes++] = (byte)(len >> 7 & 0x7F); + if (len > 0x3FFF) { + buf[bytes-1] |= 0x80; + buf[bytes++] = (byte)(len >> 14 & 0xFF); + } + } + rec.write(buf, 0, bytes); + } + + public void startTiming() { + timing = true; + + // Carry over up to 1s worth of previous rate for smoothing. + + if (timeWaitedIn100us > 10000) { + timedKbits = timedKbits * 10000 / timeWaitedIn100us; + timeWaitedIn100us = 10000; + } + } + + public void stopTiming() { + timing = false; + if (timeWaitedIn100us < timedKbits/2) + timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s + } + + public long kbitsPerSecond() { + return timedKbits * 10000 / timeWaitedIn100us; + } + + public long timeWaited() { + return timeWaitedIn100us; + } + + // + // Methods for reading data via our DataInputStream member variable (is). + // + // In addition to reading data, the readFully() methods updates variables + // used to estimate data throughput. + // + + public void readFully(byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + public void readFully(byte b[], int off, int len) throws IOException { + long before = 0; + if (timing) + before = System.currentTimeMillis(); + + is.readFully(b, off, len); + + if (timing) { + long after = System.currentTimeMillis(); + long newTimeWaited = (after - before) * 10; + int newKbits = len * 8 / 1000; + + // limit rate to between 10kbit/s and 40Mbit/s + + if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000; + if (newTimeWaited < newKbits/4) newTimeWaited = newKbits/4; + + timeWaitedIn100us += newTimeWaited; + timedKbits += newKbits; + } + + numBytesRead += len; + } + + final int available() throws IOException { + return is.available(); + } + + // FIXME: DataInputStream::skipBytes() is not guaranteed to skip + // exactly n bytes. Probably we don't want to use this method. + final int skipBytes(int n) throws IOException { + int r = is.skipBytes(n); + numBytesRead += r; + return r; + } + + final int readU8() throws IOException { + int r = is.readUnsignedByte(); + numBytesRead++; + return r; + } + + final int readU16() throws IOException { + int r = is.readUnsignedShort(); + numBytesRead += 2; + return r; + } + + final int readU32() throws IOException { + int r = is.readInt(); + numBytesRead += 4; + return r; + } + + void chsock(Socket sock){ + try{ + is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),16384)); + }catch(IOException e){} + } + + +} + diff -r 000000000000 -r e04119c40b9b src/SessionRecorder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SessionRecorder.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,193 @@ +// +// Copyright (C) 2002 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// SessionRecorder is a class to write FBS (FrameBuffer Stream) files. +// FBS files are used to save RFB sessions for later playback. +// + +import java.io.*; + +class SessionRecorder { + + protected FileOutputStream f; + protected DataOutputStream df; + protected long startTime, lastTimeOffset; + + protected byte[] buffer; + protected int bufferSize; + protected int bufferBytes; + + public SessionRecorder(String name, int bufsize) throws IOException { + f = new FileOutputStream(name); + df = new DataOutputStream(f); + startTime = System.currentTimeMillis(); + lastTimeOffset = 0; + + bufferSize = bufsize; + bufferBytes = 0; + buffer = new byte[bufferSize]; + } + + public SessionRecorder(String name) throws IOException { + this(name, 65536); + } + + // + // Close the file, free resources. + // + + public void close() throws IOException { + try { + flush(); + } catch (IOException e) { + } + + df = null; + f.close(); + f = null; + buffer = null; + } + + // + // Write the FBS file header as defined in the rfbproxy utility. + // + + public void writeHeader() throws IOException { + df.write("FBS 001.000\n".getBytes()); + } + + // + // Write one byte. + // + + public void writeByte(int b) throws IOException { + prepareWriting(); + buffer[bufferBytes++] = (byte)b; + } + + // + // Write 16-bit value, big-endian. + // + + public void writeShortBE(int v) throws IOException { + prepareWriting(); + buffer[bufferBytes++] = (byte)(v >> 8); + buffer[bufferBytes++] = (byte)v; + } + + // + // Write 32-bit value, big-endian. + // + + public void writeIntBE(int v) throws IOException { + prepareWriting(); + buffer[bufferBytes] = (byte)(v >> 24); + buffer[bufferBytes + 1] = (byte)(v >> 16); + buffer[bufferBytes + 2] = (byte)(v >> 8); + buffer[bufferBytes + 3] = (byte)v; + bufferBytes += 4; + } + + // + // Write 16-bit value, little-endian. + // + + public void writeShortLE(int v) throws IOException { + prepareWriting(); + buffer[bufferBytes++] = (byte)v; + buffer[bufferBytes++] = (byte)(v >> 8); + } + + // + // Write 32-bit value, little-endian. + // + + public void writeIntLE(int v) throws IOException { + prepareWriting(); + buffer[bufferBytes] = (byte)v; + buffer[bufferBytes + 1] = (byte)(v >> 8); + buffer[bufferBytes + 2] = (byte)(v >> 16); + buffer[bufferBytes + 3] = (byte)(v >> 24); + bufferBytes += 4; + } + + // + // Write byte arrays. + // + + public void write(byte b[], int off, int len) throws IOException { + prepareWriting(); + while (len > 0) { + if (bufferBytes > bufferSize - 4) + flush(false); + + int partLen; + if (bufferBytes + len > bufferSize) { + partLen = bufferSize - bufferBytes; + } else { + partLen = len; + } + System.arraycopy(b, off, buffer, bufferBytes, partLen); + bufferBytes += partLen; + off += partLen; + len -= partLen; + } + } + + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + // + // Flush the output. This method saves buffered data in the + // underlying file object adding data sizes and timestamps. If the + // updateTimeOffset is set to false, then the current time offset + // will not be changed for next write operation. + // + + public void flush(boolean updateTimeOffset) throws IOException { + if (bufferBytes > 0) { + df.writeInt(bufferBytes); + df.write(buffer, 0, (bufferBytes + 3) & 0x7FFFFFFC); + df.writeInt((int)lastTimeOffset); + bufferBytes = 0; + if (updateTimeOffset) + lastTimeOffset = -1; + } + } + + public void flush() throws IOException { + flush(true); + } + + // + // Before writing any data, remember time offset and flush the + // buffer before it becomes full. + // + + protected void prepareWriting() throws IOException { + if (lastTimeOffset == -1) + lastTimeOffset = System.currentTimeMillis() - startTime; + if (bufferBytes > bufferSize - 4) + flush(false); + } + +} + diff -r 000000000000 -r e04119c40b9b src/SocketFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SocketFactory.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,36 @@ +// +// Copyright (C) 2002 HorizonLive.com, Inc. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// SocketFactory.java describes an interface used to substitute the +// standard Socket class by its alternative implementations. +// + +import java.applet.*; +import java.net.*; +import java.io.*; + +public interface SocketFactory { + + public Socket createSocket(String host, int port, Applet applet) + throws IOException; + + public Socket createSocket(String host, int port, String[] args) + throws IOException; +} diff -r 000000000000 -r e04119c40b9b src/VncCanvas.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/VncCanvas.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,1888 @@ +// +// Copyright (C) 2004 Horizon Wimba. All Rights Reserved. +// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved. +// Copyright (C) 2000 Tridia Corporation. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.io.*; +import java.lang.*; +import java.util.zip.*; + + +// +// VncCanvas is a subclass of Canvas which draws a VNC desktop on it. +// + +class VncCanvas extends Canvas + implements KeyListener, MouseListener, MouseMotionListener { + + VncViewer viewer; + RfbProto rfb; + ColorModel cm8, cm24; + Color[] colors; + int bytesPixel; + + int maxWidth = 0, maxHeight = 0; + int scalingFactor; + int scaledWidth, scaledHeight; + + Image memImage; + Graphics memGraphics; + + Image rawPixelsImage; + MemoryImageSource pixelsSource; + byte[] pixels8; + int[] pixels24; + + // Update statistics. + long statStartTime; // time on first framebufferUpdateRequest + int statNumUpdates; // counter for FramebufferUpdate messages + int statNumTotalRects; // rectangles in FramebufferUpdate messages + int statNumPixelRects; // the same, but excluding pseudo-rectangles + int statNumRectsTight; // Tight-encoded rectangles (including JPEG) + int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles + int statNumRectsZRLE; // ZRLE-encoded rectangles + int statNumRectsHextile; // Hextile-encoded rectangles + int statNumRectsRaw; // Raw-encoded rectangles + int statNumRectsCopy; // CopyRect rectangles + int statNumBytesEncoded; // number of bytes in updates, as received + int statNumBytesDecoded; // number of bytes, as if Raw encoding was used + + // ZRLE encoder's data. + byte[] zrleBuf; + int zrleBufLen = 0; + byte[] zrleTilePixels8; + int[] zrleTilePixels24; + ZlibInStream zrleInStream; + boolean zrleRecWarningShown = false; + + // Zlib encoder's data. + byte[] zlibBuf; + int zlibBufLen = 0; + Inflater zlibInflater; + + // Tight encoder's data. + final static int tightZlibBufferSize = 512; + Inflater[] tightInflaters; + + // Since JPEG images are loaded asynchronously, we have to remember + // their position in the framebuffer. Also, this jpegRect object is + // used for synchronization between the rfbThread and a JVM's thread + // which decodes and loads JPEG images. + Rectangle jpegRect; + + // True if we process keyboard and mouse events. + boolean inputEnabled; + + // + // The constructors. + // + + public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_) + throws IOException { + + viewer = v; + maxWidth = maxWidth_; + maxHeight = maxHeight_; + + rfb = viewer.rfb; + scalingFactor = viewer.options.scalingFactor; + + tightInflaters = new Inflater[4]; + + cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); + cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); + + colors = new Color[256]; + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8.getRGB(i)); + + setPixelFormat(); + + inputEnabled = false; + if (!viewer.options.viewOnly) + enableInput(true); + + // Keyboard listener is enabled even in view-only mode, to catch + // 'r' or 'R' key presses used to request screen update. + addKeyListener(this); + } + + public VncCanvas(VncViewer v) throws IOException { + this(v, 0, 0); + } + + // + // Callback methods to determine geometry of our Component. + // + + public Dimension getPreferredSize() { + return new Dimension(scaledWidth, scaledHeight); + } + + public Dimension getMinimumSize() { + return new Dimension(scaledWidth, scaledHeight); + } + + public Dimension getMaximumSize() { + return new Dimension(scaledWidth, scaledHeight); + } + + // + // All painting is performed here. + // + + public void update(Graphics g) { + paint(g); + } + + public void paint(Graphics g) { + synchronized(memImage) { + if (rfb.framebufferWidth == scaledWidth) { + g.drawImage(memImage, 0, 0, null); + } else { + paintScaledFrameBuffer(g); + } + } + if (showSoftCursor) { + int x0 = cursorX - hotX, y0 = cursorY - hotY; + Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight); + if (r.intersects(g.getClipBounds())) { + g.drawImage(softCursor, x0, y0, null); + } + } + } + + public void paintScaledFrameBuffer(Graphics g) { + g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null); + } + + // + // Override the ImageObserver interface method to handle drawing of + // JPEG-encoded data. + // + + public boolean imageUpdate(Image img, int infoflags, + int x, int y, int width, int height) { + if ((infoflags & (ALLBITS | ABORT)) == 0) { + return true; // We need more image data. + } else { + // If the whole image is available, draw it now. + if ((infoflags & ALLBITS) != 0) { + if (jpegRect != null) { + synchronized(jpegRect) { + memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null); + scheduleRepaint(jpegRect.x, jpegRect.y, + jpegRect.width, jpegRect.height); + jpegRect.notify(); + } + } + } + return false; // All image data was processed. + } + } + + // + // Start/stop receiving mouse events. Keyboard events are received + // even in view-only mode, because we want to map the 'r' key to the + // screen refreshing function. + // + + public synchronized void enableInput(boolean enable) { + if (enable && !inputEnabled) { + inputEnabled = true; + addMouseListener(this); + addMouseMotionListener(this); + if (viewer.showControls) { + viewer.buttonPanel.enableRemoteAccessControls(true); + } + createSoftCursor(); // scaled cursor + } else if (!enable && inputEnabled) { + inputEnabled = false; + removeMouseListener(this); + removeMouseMotionListener(this); + if (viewer.showControls) { + viewer.buttonPanel.enableRemoteAccessControls(false); + } + createSoftCursor(); // non-scaled cursor + } + } + + public void setPixelFormat() throws IOException { + if (viewer.options.eightBitColors) { + rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6); + bytesPixel = 1; + } else { + rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0); + bytesPixel = 4; + } + updateFramebufferSize(); + } + + void updateFramebufferSize() { + + // Useful shortcuts. + int fbWidth = rfb.framebufferWidth; + int fbHeight = rfb.framebufferHeight; + + // Calculate scaling factor for auto scaling. + if (maxWidth > 0 && maxHeight > 0) { + int f1 = maxWidth * 100 / fbWidth; + int f2 = maxHeight * 100 / fbHeight; + scalingFactor = Math.min(f1, f2); + if (scalingFactor > 100) + scalingFactor = 100; + System.out.println("Scaling desktop at " + scalingFactor + "%"); + } + + // Update scaled framebuffer geometry. + scaledWidth = (fbWidth * scalingFactor + 50) / 100; + scaledHeight = (fbHeight * scalingFactor + 50) / 100; + + // Create new off-screen image either if it does not exist, or if + // its geometry should be changed. It's not necessary to replace + // existing image if only pixel format should be changed. + if (memImage == null) { + memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); + memGraphics = memImage.getGraphics(); + } else if (memImage.getWidth(null) != fbWidth || + memImage.getHeight(null) != fbHeight) { + synchronized(memImage) { + memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); + memGraphics = memImage.getGraphics(); + } + } + + // Images with raw pixels should be re-allocated on every change + // of geometry or pixel format. + if (bytesPixel == 1) { + + pixels24 = null; + pixels8 = new byte[fbWidth * fbHeight]; + + pixelsSource = + new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth); + + zrleTilePixels24 = null; + zrleTilePixels8 = new byte[64 * 64]; + + } else { + + pixels8 = null; + pixels24 = new int[fbWidth * fbHeight]; + + pixelsSource = + new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth); + + zrleTilePixels8 = null; + zrleTilePixels24 = new int[64 * 64]; + + } + pixelsSource.setAnimated(true); + rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource); + + // Update the size of desktop containers. + if (viewer.inSeparateFrame) { + if (viewer.desktopScrollPane != null) + resizeDesktopFrame(); + } else { + setSize(scaledWidth, scaledHeight); + } + viewer.moveFocusToDesktop(); + } + + void resizeDesktopFrame() { + setSize(scaledWidth, scaledHeight); + + // FIXME: Find a better way to determine correct size of a + // ScrollPane. -- const + Insets insets = viewer.desktopScrollPane.getInsets(); + viewer.desktopScrollPane.setSize(scaledWidth + + 2 * Math.min(insets.left, insets.right), + scaledHeight + + 2 * Math.min(insets.top, insets.bottom)); + + viewer.vncFrame.pack(); + + // Try to limit the frame size to the screen size. + + Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize(); + Dimension frameSize = viewer.vncFrame.getSize(); + Dimension newSize = frameSize; + + // Reduce Screen Size by 30 pixels in each direction; + // This is a (poor) attempt to account for + // 1) Menu bar on Macintosh (should really also account for + // Dock on OSX). Usually 22px on top of screen. + // 2) Taxkbar on Windows (usually about 28 px on bottom) + // 3) Other obstructions. + + screenSize.height -= 30; + screenSize.width -= 30; + + boolean needToResizeFrame = false; + if (frameSize.height > screenSize.height) { + newSize.height = screenSize.height; + needToResizeFrame = true; + } + if (frameSize.width > screenSize.width) { + newSize.width = screenSize.width; + needToResizeFrame = true; + } + if (needToResizeFrame) { + viewer.vncFrame.setSize(newSize); + } + + viewer.desktopScrollPane.doLayout(); + } + + // + // processNormalProtocol() - executed by the rfbThread to deal with the + // RFB socket. + // + + public void processNormalProtocol() throws Exception { + + // Start/stop session recording if necessary. + viewer.checkRecordingStatus(); + + rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth, + rfb.framebufferHeight, false); + + resetStats(); + boolean statsRestarted = false; + + // + // main dispatch loop + // + + while (true) { + + + if(rfb.TEST){ + System.out.println("rfb.available()="+rfb.available()); + byte b[] = new byte[rfb.available()]; + rfb.readFully(b); + rfb.cliSock.getOutputStream().write(b, 0, b.length); + } + + + // Read message type from the server. + int msgType = rfb.readServerMessageType(); + + + // Process the message depending on its type. + switch (msgType) { + case RfbProto.FramebufferUpdate: + + if (statNumUpdates == viewer.debugStatsExcludeUpdates && + !statsRestarted) { + resetStats(); + statsRestarted = true; + } else if (statNumUpdates == viewer.debugStatsMeasureUpdates && + statsRestarted) { + viewer.disconnect(); + } + + rfb.readFramebufferUpdate(); + statNumUpdates++; + + boolean cursorPosReceived = false; + + for (int i = 0; i < rfb.updateNRects; i++) { + + rfb.readFramebufferUpdateRectHdr(); + statNumTotalRects++; + int rx = rfb.updateRectX, ry = rfb.updateRectY; + int rw = rfb.updateRectW, rh = rfb.updateRectH; + + if (rfb.updateRectEncoding == rfb.EncodingLastRect) + break; + + if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) { + rfb.setFramebufferSize(rw, rh); + updateFramebufferSize(); + break; + } + + if (rfb.updateRectEncoding == rfb.EncodingXCursor || + rfb.updateRectEncoding == rfb.EncodingRichCursor) { + handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh); + continue; + } + + if (rfb.updateRectEncoding == rfb.EncodingPointerPos) { + softCursorMove(rx, ry); + cursorPosReceived = true; + continue; + } + + long numBytesReadBefore = rfb.getNumBytesRead(); + + rfb.startTiming(); + + switch (rfb.updateRectEncoding) { + case RfbProto.EncodingRaw: + statNumRectsRaw++; + handleRawRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingCopyRect: + statNumRectsCopy++; + handleCopyRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingRRE: + handleRRERect(rx, ry, rw, rh); + break; + case RfbProto.EncodingCoRRE: + handleCoRRERect(rx, ry, rw, rh); + break; + case RfbProto.EncodingHextile: + statNumRectsHextile++; + handleHextileRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingZRLE: + statNumRectsZRLE++; + handleZRLERect(rx, ry, rw, rh); + break; + case RfbProto.EncodingZlib: + handleZlibRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingTight: + statNumRectsTight++; + handleTightRect(rx, ry, rw, rh); + break; + default: + throw new Exception("Unknown RFB rectangle encoding " + + rfb.updateRectEncoding); + } + + rfb.stopTiming(); + + statNumPixelRects++; + statNumBytesDecoded += rw * rh * bytesPixel; + statNumBytesEncoded += + (int)(rfb.getNumBytesRead() - numBytesReadBefore); + } + + boolean fullUpdateNeeded = false; + + // Start/stop session recording if necessary. Request full + // update if a new session file was opened. + if (viewer.checkRecordingStatus()) + fullUpdateNeeded = true; + + // Defer framebuffer update request if necessary. But wake up + // immediately on keyboard or mouse event. Also, don't sleep + // if there is some data to receive, or if the last update + // included a PointerPos message. + if (viewer.deferUpdateRequests > 0 && + rfb.available() == 0 && !cursorPosReceived) { + synchronized(rfb) { + try { + rfb.wait(viewer.deferUpdateRequests); + } catch (InterruptedException e) { + } + } + } + + viewer.autoSelectEncodings(); + + // Before requesting framebuffer update, check if the pixel + // format should be changed. + if (viewer.options.eightBitColors != (bytesPixel == 1)) { + // Pixel format should be changed. + setPixelFormat(); + fullUpdateNeeded = true; + } + + // Request framebuffer update if needed. + int w = rfb.framebufferWidth; + int h = rfb.framebufferHeight; + rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded); + + break; + + case RfbProto.SetColourMapEntries: + throw new Exception("Can't handle SetColourMapEntries message"); + + case RfbProto.Bell: + Toolkit.getDefaultToolkit().beep(); + break; + + case RfbProto.ServerCutText: + String s = rfb.readServerCutText(); + viewer.clipboard.setCutText(s); + break; + + default: + throw new Exception("Unknown RFB message type " + msgType); + } + } + } + + + // + // Handle a raw rectangle. The second form with paint==false is used + // by the Hextile decoder for raw-encoded tiles. + // + + void handleRawRect(int x, int y, int w, int h) throws IOException { + handleRawRect(x, y, w, h, true); + } + + void handleRawRect(int x, int y, int w, int h, boolean paint) + throws IOException { + + if (bytesPixel == 1) { + for (int dy = y; dy < y + h; dy++) { + rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); + if (rfb.rec != null) { + rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); + } + } + } else { + byte[] buf = new byte[w * 4]; + int i, offset; + for (int dy = y; dy < y + h; dy++) { + rfb.readFully(buf); + if (rfb.rec != null) { + rfb.rec.write(buf); + } + offset = dy * rfb.framebufferWidth + x; + for (i = 0; i < w; i++) { + pixels24[offset + i] = + (buf[i * 4 + 2] & 0xFF) << 16 | + (buf[i * 4 + 1] & 0xFF) << 8 | + (buf[i * 4] & 0xFF); + } + } + } + + handleUpdatedPixels(x, y, w, h); + if (paint) + scheduleRepaint(x, y, w, h); + } + + // + // Handle a CopyRect rectangle. + // + + void handleCopyRect(int x, int y, int w, int h) throws IOException { + + rfb.readCopyRect(); + memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h, + x - rfb.copyRectSrcX, y - rfb.copyRectSrcY); + + scheduleRepaint(x, y, w, h); + } + + // + // Handle an RRE-encoded rectangle. + // + + void handleRRERect(int x, int y, int w, int h) throws IOException { + + int nSubrects = rfb.readU32(); + + byte[] bg_buf = new byte[bytesPixel]; + rfb.readFully(bg_buf); + Color pixel; + if (bytesPixel == 1) { + pixel = colors[bg_buf[0] & 0xFF]; + } else { + pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF); + } + memGraphics.setColor(pixel); + memGraphics.fillRect(x, y, w, h); + + byte[] buf = new byte[nSubrects * (bytesPixel + 8)]; + rfb.readFully(buf); + DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf)); + + if (rfb.rec != null) { + rfb.rec.writeIntBE(nSubrects); + rfb.rec.write(bg_buf); + rfb.rec.write(buf); + } + + int sx, sy, sw, sh; + + for (int j = 0; j < nSubrects; j++) { + if (bytesPixel == 1) { + pixel = colors[ds.readUnsignedByte()]; + } else { + ds.skip(4); + pixel = new Color(buf[j*12+2] & 0xFF, + buf[j*12+1] & 0xFF, + buf[j*12] & 0xFF); + } + sx = x + ds.readUnsignedShort(); + sy = y + ds.readUnsignedShort(); + sw = ds.readUnsignedShort(); + sh = ds.readUnsignedShort(); + + memGraphics.setColor(pixel); + memGraphics.fillRect(sx, sy, sw, sh); + } + + scheduleRepaint(x, y, w, h); + } + + // + // Handle a CoRRE-encoded rectangle. + // + + void handleCoRRERect(int x, int y, int w, int h) throws IOException { + int nSubrects = rfb.readU32(); + + byte[] bg_buf = new byte[bytesPixel]; + rfb.readFully(bg_buf); + Color pixel; + if (bytesPixel == 1) { + pixel = colors[bg_buf[0] & 0xFF]; + } else { + pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF); + } + memGraphics.setColor(pixel); + memGraphics.fillRect(x, y, w, h); + + byte[] buf = new byte[nSubrects * (bytesPixel + 4)]; + rfb.readFully(buf); + + if (rfb.rec != null) { + rfb.rec.writeIntBE(nSubrects); + rfb.rec.write(bg_buf); + rfb.rec.write(buf); + } + + int sx, sy, sw, sh; + int i = 0; + + for (int j = 0; j < nSubrects; j++) { + if (bytesPixel == 1) { + pixel = colors[buf[i++] & 0xFF]; + } else { + pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF); + i += 4; + } + sx = x + (buf[i++] & 0xFF); + sy = y + (buf[i++] & 0xFF); + sw = buf[i++] & 0xFF; + sh = buf[i++] & 0xFF; + + memGraphics.setColor(pixel); + memGraphics.fillRect(sx, sy, sw, sh); + } + + scheduleRepaint(x, y, w, h); + } + + // + // Handle a Hextile-encoded rectangle. + // + + // These colors should be kept between handleHextileSubrect() calls. + private Color hextile_bg, hextile_fg; + + void handleHextileRect(int x, int y, int w, int h) throws IOException { + + hextile_bg = new Color(0); + hextile_fg = new Color(0); + + for (int ty = y; ty < y + h; ty += 16) { + int th = 16; + if (y + h - ty < 16) + th = y + h - ty; + + for (int tx = x; tx < x + w; tx += 16) { + int tw = 16; + if (x + w - tx < 16) + tw = x + w - tx; + + handleHextileSubrect(tx, ty, tw, th); + } + + // Finished with a row of tiles, now let's show it. + scheduleRepaint(x, y, w, h); + } + } + + // + // Handle one tile in the Hextile-encoded data. + // + + void handleHextileSubrect(int tx, int ty, int tw, int th) + throws IOException { + + int subencoding = rfb.readU8(); + if (rfb.rec != null) { + rfb.rec.writeByte(subencoding); + } + + // Is it a raw-encoded sub-rectangle? + if ((subencoding & rfb.HextileRaw) != 0) { + handleRawRect(tx, ty, tw, th, false); + return; + } + + // Read and draw the background if specified. + byte[] cbuf = new byte[bytesPixel]; + if ((subencoding & rfb.HextileBackgroundSpecified) != 0) { + rfb.readFully(cbuf); + if (bytesPixel == 1) { + hextile_bg = colors[cbuf[0] & 0xFF]; + } else { + hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF); + } + if (rfb.rec != null) { + rfb.rec.write(cbuf); + } + } + memGraphics.setColor(hextile_bg); + memGraphics.fillRect(tx, ty, tw, th); + + // Read the foreground color if specified. + if ((subencoding & rfb.HextileForegroundSpecified) != 0) { + rfb.readFully(cbuf); + if (bytesPixel == 1) { + hextile_fg = colors[cbuf[0] & 0xFF]; + } else { + hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF); + } + if (rfb.rec != null) { + rfb.rec.write(cbuf); + } + } + + // Done with this tile if there is no sub-rectangles. + if ((subencoding & rfb.HextileAnySubrects) == 0) + return; + + int nSubrects = rfb.readU8(); + int bufsize = nSubrects * 2; + if ((subencoding & rfb.HextileSubrectsColoured) != 0) { + bufsize += nSubrects * bytesPixel; + } + byte[] buf = new byte[bufsize]; + rfb.readFully(buf); + if (rfb.rec != null) { + rfb.rec.writeByte(nSubrects); + rfb.rec.write(buf); + } + + int b1, b2, sx, sy, sw, sh; + int i = 0; + + if ((subencoding & rfb.HextileSubrectsColoured) == 0) { + + // Sub-rectangles are all of the same color. + memGraphics.setColor(hextile_fg); + for (int j = 0; j < nSubrects; j++) { + b1 = buf[i++] & 0xFF; + b2 = buf[i++] & 0xFF; + sx = tx + (b1 >> 4); + sy = ty + (b1 & 0xf); + sw = (b2 >> 4) + 1; + sh = (b2 & 0xf) + 1; + memGraphics.fillRect(sx, sy, sw, sh); + } + } else if (bytesPixel == 1) { + + // BGR233 (8-bit color) version for colored sub-rectangles. + for (int j = 0; j < nSubrects; j++) { + hextile_fg = colors[buf[i++] & 0xFF]; + b1 = buf[i++] & 0xFF; + b2 = buf[i++] & 0xFF; + sx = tx + (b1 >> 4); + sy = ty + (b1 & 0xf); + sw = (b2 >> 4) + 1; + sh = (b2 & 0xf) + 1; + memGraphics.setColor(hextile_fg); + memGraphics.fillRect(sx, sy, sw, sh); + } + + } else { + + // Full-color (24-bit) version for colored sub-rectangles. + for (int j = 0; j < nSubrects; j++) { + hextile_fg = new Color(buf[i+2] & 0xFF, + buf[i+1] & 0xFF, + buf[i] & 0xFF); + i += 4; + b1 = buf[i++] & 0xFF; + b2 = buf[i++] & 0xFF; + sx = tx + (b1 >> 4); + sy = ty + (b1 & 0xf); + sw = (b2 >> 4) + 1; + sh = (b2 & 0xf) + 1; + memGraphics.setColor(hextile_fg); + memGraphics.fillRect(sx, sy, sw, sh); + } + + } + } + + // + // Handle a ZRLE-encoded rectangle. + // + // FIXME: Currently, session recording is not fully supported for ZRLE. + // + + void handleZRLERect(int x, int y, int w, int h) throws Exception { + + if (zrleInStream == null) + zrleInStream = new ZlibInStream(); + + int nBytes = rfb.readU32(); + if (nBytes > 64 * 1024 * 1024) + throw new Exception("ZRLE decoder: illegal compressed data size"); + + if (zrleBuf == null || zrleBufLen < nBytes) { + zrleBufLen = nBytes + 4096; + zrleBuf = new byte[zrleBufLen]; + } + + // FIXME: Do not wait for all the data before decompression. + rfb.readFully(zrleBuf, 0, nBytes); + + + if (rfb.rec != null) { + if (rfb.recordFromBeginning) { + rfb.rec.writeIntBE(nBytes); + rfb.rec.write(zrleBuf, 0, nBytes); + } else if (!zrleRecWarningShown) { + System.out.println("Warning: ZRLE session can be recorded" + + " only from the beginning"); + System.out.println("Warning: Recorded file may be corrupted"); + zrleRecWarningShown = true; + } + } + + zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes); + + for (int ty = y; ty < y+h; ty += 64) { + + int th = Math.min(y+h-ty, 64); + + for (int tx = x; tx < x+w; tx += 64) { + + int tw = Math.min(x+w-tx, 64); + + int mode = zrleInStream.readU8(); + boolean rle = (mode & 128) != 0; + int palSize = mode & 127; + int[] palette = new int[128]; + + readZrlePalette(palette, palSize); + + if (palSize == 1) { + int pix = palette[0]; + Color c = (bytesPixel == 1) ? + colors[pix] : new Color(0xFF000000 | pix); + memGraphics.setColor(c); + memGraphics.fillRect(tx, ty, tw, th); + continue; + } + + if (!rle) { + if (palSize == 0) { + readZrleRawPixels(tw, th); + } else { + readZrlePackedPixels(tw, th, palette, palSize); + } + } else { + if (palSize == 0) { + readZrlePlainRLEPixels(tw, th); + } else { + readZrlePackedRLEPixels(tw, th, palette); + } + } + handleUpdatedZrleTile(tx, ty, tw, th); + } + } + + zrleInStream.reset(); + + scheduleRepaint(x, y, w, h); + } + + int readPixel(InStream is) throws Exception { + int pix; + + if (bytesPixel == 1) { + + + pix = is.readU8(); + } else { + int p1 = is.readU8(); + int p2 = is.readU8(); + int p3 = is.readU8(); + pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF); + } + return pix; + } + + void readPixels(InStream is, int[] dst, int count) throws Exception { + int pix; + if (bytesPixel == 1) { + byte[] buf = new byte[count]; + is.readBytes(buf, 0, count); + for (int i = 0; i < count; i++) { + dst[i] = (int)buf[i] & 0xFF; + } + } else { + byte[] buf = new byte[count * 3]; + is.readBytes(buf, 0, count * 3); + for (int i = 0; i < count; i++) { + dst[i] = ((buf[i*3+2] & 0xFF) << 16 | + (buf[i*3+1] & 0xFF) << 8 | + (buf[i*3] & 0xFF)); + /* + dst[i] = (0x00 << 16 | + 0x00 << 8 | + 0xFF); + */ + + + + } + } + } + + void readZrlePalette(int[] palette, int palSize) throws Exception { + readPixels(zrleInStream, palette, palSize); + } + + void readZrleRawPixels(int tw, int th) throws Exception { + if (bytesPixel == 1) { + zrleInStream.readBytes(zrleTilePixels8, 0, tw * th); + } else { + readPixels(zrleInStream, zrleTilePixels24, tw * th); /// + } + } + + void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) + throws Exception { + + int bppp = ((palSize > 16) ? 8 : + ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1))); + int ptr = 0; + + for (int i = 0; i < th; i++) { + int eol = ptr + tw; + int b = 0; + int nbits = 0; + + while (ptr < eol) { + if (nbits == 0) { + b = zrleInStream.readU8(); + nbits = 8; + } + nbits -= bppp; + int index = (b >> nbits) & ((1 << bppp) - 1) & 127; + if (bytesPixel == 1) { + zrleTilePixels8[ptr++] = (byte)palette[index]; + } else { + zrleTilePixels24[ptr++] = palette[index]; + } + } + } + } + + void readZrlePlainRLEPixels(int tw, int th) throws Exception { + int ptr = 0; + int end = ptr + tw * th; + while (ptr < end) { + int pix = readPixel(zrleInStream); + int len = 1; + int b; + do { + b = zrleInStream.readU8(); + len += b; + } while (b == 255); + + if (!(len <= end - ptr)) + throw new Exception("ZRLE decoder: assertion failed" + + " (len <= end-ptr)"); + + if (bytesPixel == 1) { + while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix; + } else { + while (len-- > 0) zrleTilePixels24[ptr++] = pix; + } + } + } + + void readZrlePackedRLEPixels(int tw, int th, int[] palette) + throws Exception { + + int ptr = 0; + int end = ptr + tw * th; + while (ptr < end) { + int index = zrleInStream.readU8(); + int len = 1; + if ((index & 128) != 0) { + int b; + do { + b = zrleInStream.readU8(); + len += b; + } while (b == 255); + + if (!(len <= end - ptr)) + throw new Exception("ZRLE decoder: assertion failed" + + " (len <= end - ptr)"); + } + + index &= 127; + int pix = palette[index]; + + if (bytesPixel == 1) { + while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix; + } else { + while (len-- > 0) zrleTilePixels24[ptr++] = pix; + } + } + } + + // + // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update. + // + + void handleUpdatedZrleTile(int x, int y, int w, int h) { + Object src, dst; + if (bytesPixel == 1) { + src = zrleTilePixels8; dst = pixels8; + } else { + src = zrleTilePixels24; dst = pixels24; + } + int offsetSrc = 0; + int offsetDst = (y * rfb.framebufferWidth + x); + for (int j = 0; j < h; j++) { + System.arraycopy(src, offsetSrc, dst, offsetDst, w); + offsetSrc += w; + offsetDst += rfb.framebufferWidth; + } + handleUpdatedPixels(x, y, w, h); + } + + // + // Handle a Zlib-encoded rectangle. + // + + void handleZlibRect(int x, int y, int w, int h) throws Exception { + + int nBytes = rfb.readU32(); + + if (zlibBuf == null || zlibBufLen < nBytes) { + zlibBufLen = nBytes * 2; + zlibBuf = new byte[zlibBufLen]; + } + + rfb.readFully(zlibBuf, 0, nBytes); + + if (rfb.rec != null && rfb.recordFromBeginning) { + rfb.rec.writeIntBE(nBytes); + rfb.rec.write(zlibBuf, 0, nBytes); + } + + if (zlibInflater == null) { + zlibInflater = new Inflater(); + } + zlibInflater.setInput(zlibBuf, 0, nBytes); + + if (bytesPixel == 1) { + for (int dy = y; dy < y + h; dy++) { + zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w); + if (rfb.rec != null && !rfb.recordFromBeginning) + rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); + } + } else { + byte[] buf = new byte[w * 4]; + int i, offset; + for (int dy = y; dy < y + h; dy++) { + zlibInflater.inflate(buf); + offset = dy * rfb.framebufferWidth + x; + for (i = 0; i < w; i++) { + pixels24[offset + i] = + (buf[i * 4 + 2] & 0xFF) << 16 | + (buf[i * 4 + 1] & 0xFF) << 8 | + (buf[i * 4] & 0xFF); + } + if (rfb.rec != null && !rfb.recordFromBeginning) + rfb.rec.write(buf); + } + } + + handleUpdatedPixels(x, y, w, h); + scheduleRepaint(x, y, w, h); + } + + // + // Handle a Tight-encoded rectangle. + // + + void handleTightRect(int x, int y, int w, int h) throws Exception { + + int comp_ctl = rfb.readU8(); + if (rfb.rec != null) { + if (rfb.recordFromBeginning || + comp_ctl == (rfb.TightFill << 4) || + comp_ctl == (rfb.TightJpeg << 4)) { + // Send data exactly as received. + rfb.rec.writeByte(comp_ctl); + } else { + // Tell the decoder to flush each of the four zlib streams. + rfb.rec.writeByte(comp_ctl | 0x0F); + } + } + + // Flush zlib streams if we are told by the server to do so. + for (int stream_id = 0; stream_id < 4; stream_id++) { + if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) { + tightInflaters[stream_id] = null; + } + comp_ctl >>= 1; + } + + // Check correctness of subencoding value. + if (comp_ctl > rfb.TightMaxSubencoding) { + throw new Exception("Incorrect tight subencoding: " + comp_ctl); + } + + // Handle solid-color rectangles. + if (comp_ctl == rfb.TightFill) { + + if (bytesPixel == 1) { + int idx = rfb.readU8(); + memGraphics.setColor(colors[idx]); + if (rfb.rec != null) { + rfb.rec.writeByte(idx); + } + } else { + byte[] buf = new byte[3]; + rfb.readFully(buf); + if (rfb.rec != null) { + rfb.rec.write(buf); + } + Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 | + (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF)); + memGraphics.setColor(bg); + } + memGraphics.fillRect(x, y, w, h); + scheduleRepaint(x, y, w, h); + return; + + } + + if (comp_ctl == rfb.TightJpeg) { + + statNumRectsTightJPEG++; + + // Read JPEG data. + byte[] jpegData = new byte[rfb.readCompactLen()]; + rfb.readFully(jpegData); + if (rfb.rec != null) { + if (!rfb.recordFromBeginning) { + rfb.recordCompactLen(jpegData.length); + } + rfb.rec.write(jpegData); + } + + // Create an Image object from the JPEG data. + Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData); + + // Remember the rectangle where the image should be drawn. + jpegRect = new Rectangle(x, y, w, h); + + // Let the imageUpdate() method do the actual drawing, here just + // wait until the image is fully loaded and drawn. + synchronized(jpegRect) { + Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this); + try { + // Wait no longer than three seconds. + jpegRect.wait(3000); + } catch (InterruptedException e) { + throw new Exception("Interrupted while decoding JPEG image"); + } + } + + // Done, jpegRect is not needed any more. + jpegRect = null; + return; + + } + + // Read filter id and parameters. + int numColors = 0, rowSize = w; + byte[] palette8 = new byte[2]; + int[] palette24 = new int[256]; + boolean useGradient = false; + if ((comp_ctl & rfb.TightExplicitFilter) != 0) { + int filter_id = rfb.readU8(); + if (rfb.rec != null) { + rfb.rec.writeByte(filter_id); + } + if (filter_id == rfb.TightFilterPalette) { + numColors = rfb.readU8() + 1; + if (rfb.rec != null) { + rfb.rec.writeByte(numColors - 1); + } + if (bytesPixel == 1) { + if (numColors != 2) { + throw new Exception("Incorrect tight palette size: " + numColors); + } + rfb.readFully(palette8); + if (rfb.rec != null) { + rfb.rec.write(palette8); + } + } else { + byte[] buf = new byte[numColors * 3]; + rfb.readFully(buf); + if (rfb.rec != null) { + rfb.rec.write(buf); + } + for (int i = 0; i < numColors; i++) { + palette24[i] = ((buf[i * 3] & 0xFF) << 16 | + (buf[i * 3 + 1] & 0xFF) << 8 | + (buf[i * 3 + 2] & 0xFF)); + } + } + if (numColors == 2) + rowSize = (w + 7) / 8; + } else if (filter_id == rfb.TightFilterGradient) { + useGradient = true; + } else if (filter_id != rfb.TightFilterCopy) { + throw new Exception("Incorrect tight filter id: " + filter_id); + } + } + if (numColors == 0 && bytesPixel == 4) + rowSize *= 3; + + // Read, optionally uncompress and decode data. + int dataSize = h * rowSize; + if (dataSize < rfb.TightMinToCompress) { + // Data size is small - not compressed with zlib. + if (numColors != 0) { + // Indexed colors. + byte[] indexedData = new byte[dataSize]; + rfb.readFully(indexedData); + if (rfb.rec != null) { + rfb.rec.write(indexedData); + } + if (numColors == 2) { + // Two colors. + if (bytesPixel == 1) { + decodeMonoData(x, y, w, h, indexedData, palette8); + } else { + decodeMonoData(x, y, w, h, indexedData, palette24); + } + } else { + // 3..255 colors (assuming bytesPixel == 4). + int i = 0; + for (int dy = y; dy < y + h; dy++) { + for (int dx = x; dx < x + w; dx++) { + pixels24[dy * rfb.framebufferWidth + dx] = + palette24[indexedData[i++] & 0xFF]; + } + } + } + } else if (useGradient) { + // "Gradient"-processed data + byte[] buf = new byte[w * h * 3]; + rfb.readFully(buf); + if (rfb.rec != null) { + rfb.rec.write(buf); + } + decodeGradientData(x, y, w, h, buf); + } else { + // Raw truecolor data. + if (bytesPixel == 1) { + for (int dy = y; dy < y + h; dy++) { + rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); + if (rfb.rec != null) { + rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); + } + } + } else { + byte[] buf = new byte[w * 3]; + int i, offset; + for (int dy = y; dy < y + h; dy++) { + rfb.readFully(buf); + if (rfb.rec != null) { + rfb.rec.write(buf); + } + offset = dy * rfb.framebufferWidth + x; + for (i = 0; i < w; i++) { + pixels24[offset + i] = + (buf[i * 3] & 0xFF) << 16 | + (buf[i * 3 + 1] & 0xFF) << 8 | + (buf[i * 3 + 2] & 0xFF); + } + } + } + } + } else { + // Data was compressed with zlib. + int zlibDataLen = rfb.readCompactLen(); + byte[] zlibData = new byte[zlibDataLen]; + rfb.readFully(zlibData); + if (rfb.rec != null && rfb.recordFromBeginning) { + rfb.rec.write(zlibData); + } + int stream_id = comp_ctl & 0x03; + if (tightInflaters[stream_id] == null) { + tightInflaters[stream_id] = new Inflater(); + } + Inflater myInflater = tightInflaters[stream_id]; + myInflater.setInput(zlibData); + byte[] buf = new byte[dataSize]; + myInflater.inflate(buf); + if (rfb.rec != null && !rfb.recordFromBeginning) { + rfb.recordCompressedData(buf); + } + + if (numColors != 0) { + // Indexed colors. + if (numColors == 2) { + // Two colors. + if (bytesPixel == 1) { + decodeMonoData(x, y, w, h, buf, palette8); + } else { + decodeMonoData(x, y, w, h, buf, palette24); + } + } else { + // More than two colors (assuming bytesPixel == 4). + int i = 0; + for (int dy = y; dy < y + h; dy++) { + for (int dx = x; dx < x + w; dx++) { + pixels24[dy * rfb.framebufferWidth + dx] = + palette24[buf[i++] & 0xFF]; + } + } + } + } else if (useGradient) { + // Compressed "Gradient"-filtered data (assuming bytesPixel == 4). + decodeGradientData(x, y, w, h, buf); + } else { + // Compressed truecolor data. + if (bytesPixel == 1) { + int destOffset = y * rfb.framebufferWidth + x; + for (int dy = 0; dy < h; dy++) { + System.arraycopy(buf, dy * w, pixels8, destOffset, w); + destOffset += rfb.framebufferWidth; + } + } else { + int srcOffset = 0; + int destOffset, i; + for (int dy = 0; dy < h; dy++) { + myInflater.inflate(buf); + destOffset = (y + dy) * rfb.framebufferWidth + x; + for (i = 0; i < w; i++) { + pixels24[destOffset + i] = + (buf[srcOffset] & 0xFF) << 16 | + (buf[srcOffset + 1] & 0xFF) << 8 | + (buf[srcOffset + 2] & 0xFF); + srcOffset += 3; + } + } + } + } + } + + handleUpdatedPixels(x, y, w, h); + scheduleRepaint(x, y, w, h); + } + + // + // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions). + // + + void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) { + + int dx, dy, n; + int i = y * rfb.framebufferWidth + x; + int rowBytes = (w + 7) / 8; + byte b; + + for (dy = 0; dy < h; dy++) { + for (dx = 0; dx < w / 8; dx++) { + b = src[dy*rowBytes+dx]; + for (n = 7; n >= 0; n--) + pixels8[i++] = palette[b >> n & 1]; + } + for (n = 7; n >= 8 - w % 8; n--) { + pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1]; + } + i += (rfb.framebufferWidth - w); + } + } + + void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) { + + int dx, dy, n; + int i = y * rfb.framebufferWidth + x; + int rowBytes = (w + 7) / 8; + byte b; + + for (dy = 0; dy < h; dy++) { + for (dx = 0; dx < w / 8; dx++) { + b = src[dy*rowBytes+dx]; + for (n = 7; n >= 0; n--) + pixels24[i++] = palette[b >> n & 1]; + } + for (n = 7; n >= 8 - w % 8; n--) { + pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1]; + } + i += (rfb.framebufferWidth - w); + } + } + + // + // Decode data processed with the "Gradient" filter. + // + + void decodeGradientData (int x, int y, int w, int h, byte[] buf) { + + int dx, dy, c; + byte[] prevRow = new byte[w * 3]; + byte[] thisRow = new byte[w * 3]; + byte[] pix = new byte[3]; + int[] est = new int[3]; + + int offset = y * rfb.framebufferWidth + x; + + for (dy = 0; dy < h; dy++) { + + /* First pixel in a row */ + for (c = 0; c < 3; c++) { + pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]); + thisRow[c] = pix[c]; + } + pixels24[offset++] = + (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF); + + /* Remaining pixels of a row */ + for (dx = 1; dx < w; dx++) { + for (c = 0; c < 3; c++) { + est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) - + (prevRow[(dx-1) * 3 + c] & 0xFF)); + if (est[c] > 0xFF) { + est[c] = 0xFF; + } else if (est[c] < 0x00) { + est[c] = 0x00; + } + pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]); + thisRow[dx * 3 + c] = pix[c]; + } + pixels24[offset++] = + (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF); + } + + System.arraycopy(thisRow, 0, prevRow, 0, w * 3); + offset += (rfb.framebufferWidth - w); + } + } + + // + // Display newly updated area of pixels. + // + + void handleUpdatedPixels(int x, int y, int w, int h) { + + // Draw updated pixels of the off-screen image. + pixelsSource.newPixels(x, y, w, h); + memGraphics.setClip(x, y, w, h); + memGraphics.drawImage(rawPixelsImage, 0, 0, null); + memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight); + } + + // + // Tell JVM to repaint specified desktop area. + // + + void scheduleRepaint(int x, int y, int w, int h) { + // Request repaint, deferred if necessary. + if (rfb.framebufferWidth == scaledWidth) { + repaint(viewer.deferScreenUpdates, x, y, w, h); + } else { + int sx = x * scalingFactor / 100; + int sy = y * scalingFactor / 100; + int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1; + int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1; + repaint(viewer.deferScreenUpdates, sx, sy, sw, sh); + } + } + + // + // Handle events. + // + + public void keyPressed(KeyEvent evt) { + processLocalKeyEvent(evt); + } + public void keyReleased(KeyEvent evt) { + processLocalKeyEvent(evt); + } + public void keyTyped(KeyEvent evt) { + evt.consume(); + } + + public void mousePressed(MouseEvent evt) { + processLocalMouseEvent(evt, false); + } + public void mouseReleased(MouseEvent evt) { + processLocalMouseEvent(evt, false); + } + public void mouseMoved(MouseEvent evt) { + processLocalMouseEvent(evt, true); + } + public void mouseDragged(MouseEvent evt) { + processLocalMouseEvent(evt, true); + } + + public void processLocalKeyEvent(KeyEvent evt) { + if (viewer.rfb != null && rfb.inNormalProtocol) { + if (!inputEnabled) { + if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') && + evt.getID() == KeyEvent.KEY_PRESSED ) { + // Request screen update. + try { + rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth, + rfb.framebufferHeight, false); + } catch (IOException e) { + e.printStackTrace(); + } + } + } else { + // Input enabled. + synchronized(rfb) { + try { + rfb.writeKeyEvent(evt); + } catch (Exception e) { + e.printStackTrace(); + } + rfb.notify(); + } + } + } + // Don't ever pass keyboard events to AWT for default processing. + // Otherwise, pressing Tab would switch focus to ButtonPanel etc. + evt.consume(); + } + + public void processLocalMouseEvent(MouseEvent evt, boolean moved) { + if (viewer.rfb != null && rfb.inNormalProtocol) { + if (moved) { + softCursorMove(evt.getX(), evt.getY()); + } + if (rfb.framebufferWidth != scaledWidth) { + int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor; + int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor; + evt.translatePoint(sx - evt.getX(), sy - evt.getY()); + } + synchronized(rfb) { + try { + rfb.writePointerEvent(evt); + } catch (Exception e) { + e.printStackTrace(); + } + rfb.notify(); + } + } + } + + // + // Ignored events. + // + + public void mouseClicked(MouseEvent evt) {} + public void mouseEntered(MouseEvent evt) {} + public void mouseExited(MouseEvent evt) {} + + // + // Reset update statistics. + // + + void resetStats() { + statStartTime = System.currentTimeMillis(); + statNumUpdates = 0; + statNumTotalRects = 0; + statNumPixelRects = 0; + statNumRectsTight = 0; + statNumRectsTightJPEG = 0; + statNumRectsZRLE = 0; + statNumRectsHextile = 0; + statNumRectsRaw = 0; + statNumRectsCopy = 0; + statNumBytesEncoded = 0; + statNumBytesDecoded = 0; + } + + ////////////////////////////////////////////////////////////////// + // + // Handle cursor shape updates (XCursor and RichCursor encodings). + // + + boolean showSoftCursor = false; + + MemoryImageSource softCursorSource; + Image softCursor; + + int cursorX = 0, cursorY = 0; + int cursorWidth, cursorHeight; + int origCursorWidth, origCursorHeight; + int hotX, hotY; + int origHotX, origHotY; + + // + // Handle cursor shape update (XCursor and RichCursor encodings). + // + + synchronized void + handleCursorShapeUpdate(int encodingType, + int xhot, int yhot, int width, int height) + throws IOException { + + softCursorFree(); + + if (width * height == 0) + return; + + // Ignore cursor shape data if requested by user. + if (viewer.options.ignoreCursorUpdates) { + int bytesPerRow = (width + 7) / 8; + int bytesMaskData = bytesPerRow * height; + + if (encodingType == rfb.EncodingXCursor) { + rfb.skipBytes(6 + bytesMaskData * 2); + } else { + // rfb.EncodingRichCursor + rfb.skipBytes(width * height * bytesPixel + bytesMaskData); + } + return; + } + + // Decode cursor pixel data. + softCursorSource = decodeCursorShape(encodingType, width, height); + + // Set original (non-scaled) cursor dimensions. + origCursorWidth = width; + origCursorHeight = height; + origHotX = xhot; + origHotY = yhot; + + // Create off-screen cursor image. + createSoftCursor(); + + // Show the cursor. + showSoftCursor = true; + repaint(viewer.deferCursorUpdates, + cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight); + } + + // + // decodeCursorShape(). Decode cursor pixel data and return + // corresponding MemoryImageSource instance. + // + + synchronized MemoryImageSource + decodeCursorShape(int encodingType, int width, int height) + throws IOException { + + int bytesPerRow = (width + 7) / 8; + int bytesMaskData = bytesPerRow * height; + + int[] softCursorPixels = new int[width * height]; + + if (encodingType == rfb.EncodingXCursor) { + + // Read foreground and background colors of the cursor. + byte[] rgb = new byte[6]; + rfb.readFully(rgb); + int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 | + (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)), + (0xFF000000 | (rgb[0] & 0xFF) << 16 | + (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) }; + + // Read pixel and mask data. + byte[] pixBuf = new byte[bytesMaskData]; + rfb.readFully(pixBuf); + byte[] maskBuf = new byte[bytesMaskData]; + rfb.readFully(maskBuf); + + // Decode pixel data into softCursorPixels[]. + byte pixByte, maskByte; + int x, y, n, result; + int i = 0; + for (y = 0; y < height; y++) { + for (x = 0; x < width / 8; x++) { + pixByte = pixBuf[y * bytesPerRow + x]; + maskByte = maskBuf[y * bytesPerRow + x]; + for (n = 7; n >= 0; n--) { + if ((maskByte >> n & 1) != 0) { + result = colors[pixByte >> n & 1]; + } else { + result = 0; // Transparent pixel + } + softCursorPixels[i++] = result; + } + } + for (n = 7; n >= 8 - width % 8; n--) { + if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { + result = colors[pixBuf[y * bytesPerRow + x] >> n & 1]; + } else { + result = 0; // Transparent pixel + } + softCursorPixels[i++] = result; + } + } + + } else { + // encodingType == rfb.EncodingRichCursor + + // Read pixel and mask data. + byte[] pixBuf = new byte[width * height * bytesPixel]; + rfb.readFully(pixBuf); + byte[] maskBuf = new byte[bytesMaskData]; + rfb.readFully(maskBuf); + + // Decode pixel data into softCursorPixels[]. + byte pixByte, maskByte; + int x, y, n, result; + int i = 0; + for (y = 0; y < height; y++) { + for (x = 0; x < width / 8; x++) { + maskByte = maskBuf[y * bytesPerRow + x]; + for (n = 7; n >= 0; n--) { + if ((maskByte >> n & 1) != 0) { + if (bytesPixel == 1) { + result = cm8.getRGB(pixBuf[i]); + } else { + result = 0xFF000000 | + (pixBuf[i * 4 + 2] & 0xFF) << 16 | + (pixBuf[i * 4 + 1] & 0xFF) << 8 | + (pixBuf[i * 4] & 0xFF); + } + } else { + result = 0; // Transparent pixel + } + softCursorPixels[i++] = result; + } + } + for (n = 7; n >= 8 - width % 8; n--) { + if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { + if (bytesPixel == 1) { + result = cm8.getRGB(pixBuf[i]); + } else { + result = 0xFF000000 | + (pixBuf[i * 4 + 2] & 0xFF) << 16 | + (pixBuf[i * 4 + 1] & 0xFF) << 8 | + (pixBuf[i * 4] & 0xFF); + } + } else { + result = 0; // Transparent pixel + } + softCursorPixels[i++] = result; + } + } + + } + + return new MemoryImageSource(width, height, softCursorPixels, 0, width); + } + + // + // createSoftCursor(). Assign softCursor new Image (scaled if necessary). + // Uses softCursorSource as a source for new cursor image. + // + + synchronized void + createSoftCursor() { + + if (softCursorSource == null) + return; + + int scaleCursor = viewer.options.scaleCursor; + if (scaleCursor == 0 || !inputEnabled) + scaleCursor = 100; + + // Save original cursor coordinates. + int x = cursorX - hotX; + int y = cursorY - hotY; + int w = cursorWidth; + int h = cursorHeight; + + cursorWidth = (origCursorWidth * scaleCursor + 50) / 100; + cursorHeight = (origCursorHeight * scaleCursor + 50) / 100; + hotX = (origHotX * scaleCursor + 50) / 100; + hotY = (origHotY * scaleCursor + 50) / 100; + softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource); + + if (scaleCursor != 100) { + softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight, + Image.SCALE_SMOOTH); + } + + if (showSoftCursor) { + // Compute screen area to update. + x = Math.min(x, cursorX - hotX); + y = Math.min(y, cursorY - hotY); + w = Math.max(w, cursorWidth); + h = Math.max(h, cursorHeight); + + repaint(viewer.deferCursorUpdates, x, y, w, h); + } + } + + // + // softCursorMove(). Moves soft cursor into a particular location. + // + + synchronized void softCursorMove(int x, int y) { + int oldX = cursorX; + int oldY = cursorY; + cursorX = x; + cursorY = y; + if (showSoftCursor) { + repaint(viewer.deferCursorUpdates, + oldX - hotX, oldY - hotY, cursorWidth, cursorHeight); + repaint(viewer.deferCursorUpdates, + cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight); + } + } + + // + // softCursorFree(). Remove soft cursor, dispose resources. + // + + synchronized void softCursorFree() { + if (showSoftCursor) { + showSoftCursor = false; + softCursor = null; + softCursorSource = null; + + repaint(viewer.deferCursorUpdates, + cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight); + } + } +} diff -r 000000000000 -r e04119c40b9b src/VncCanvas2.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/VncCanvas2.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,63 @@ +// +// Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +import java.awt.*; +import java.io.*; + +// +// VncCanvas2 is a special version of VncCanvas which may use Java 2 API. +// + +class VncCanvas2 extends VncCanvas { + + public VncCanvas2(VncViewer v) throws IOException { + super(v); + disableFocusTraversalKeys(); + } + + public VncCanvas2(VncViewer v, int maxWidth_, int maxHeight_) + throws IOException { + + super(v, maxWidth_, maxHeight_); + disableFocusTraversalKeys(); + } + + public void paintScaledFrameBuffer(Graphics g) { + Graphics2D g2d = (Graphics2D)g; + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2d.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null); + } + + // + // Try to disable focus traversal keys (JVMs 1.4 and higher). + // + + private void disableFocusTraversalKeys() { + try { + Class[] argClasses = { Boolean.TYPE }; + java.lang.reflect.Method method = + getClass().getMethod("setFocusTraversalKeysEnabled", argClasses); + Object[] argObjects = { new Boolean(false) }; + method.invoke(this, argObjects); + } catch (Exception e) {} + } + +} + diff -r 000000000000 -r e04119c40b9b src/VncViewer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/VncViewer.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,1096 @@ +// +// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 2002 Constantin Kaplinsky. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This 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 software 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 software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// VncViewer.java - the VNC viewer applet. This class mainly just sets up the +// user interface, leaving it to the VncCanvas to do the actual rendering of +// a VNC desktop. +// + + import java.awt.*; + import java.awt.event.*; + import java.io.*; + import java.net.*; + +public class VncViewer extends java.applet.Applet + implements java.lang.Runnable, WindowListener { + + boolean inAnApplet = true; + boolean inSeparateFrame = false; + + // + // main() is called when run as a java program from the command line. + // It simply runs the applet inside a newly-created frame. + // + + public static void main(String[] argv) { + VncViewer v = new VncViewer(); + v.mainArgs = argv; + v.inAnApplet = false; + v.inSeparateFrame = true; + /* + if(argv.length > 1){ + v.host = argv[0]; + v.port = Integer.parseInt(argv[1]); + } + */ + v.init(); + v.start(); + } + + String[] mainArgs; + + RfbProto rfb; + Thread rfbThread; + + Frame vncFrame; + Container vncContainer; + ScrollPane desktopScrollPane; + GridBagLayout gridbag; + ButtonPanel buttonPanel; + Label connStatusLabel; + VncCanvas vc; + OptionsFrame options; + ClipboardFrame clipboard; + RecordingFrame rec; + + // Control session recording. + Object recordingSync; + String sessionFileName; + boolean recordingActive; + boolean recordingStatusChanged; + String cursorUpdatesDef; + String eightBitColorsDef; + + // Variables read from parameter values. + String socketFactory; + String host; + int port; + String passwordParam; + boolean showControls; + boolean offerRelogin; + boolean showOfflineDesktop; + int deferScreenUpdates; + int deferCursorUpdates; + int deferUpdateRequests; + int debugStatsExcludeUpdates; + int debugStatsMeasureUpdates; + + + // Reference to this applet for inter-applet communication. + public static java.applet.Applet refApplet; + + // + // init() + // + + public void init() { + + readParameters(); + + refApplet = this; + + if (inSeparateFrame) { + vncFrame = new Frame("TightVNC"); + if (!inAnApplet) { + vncFrame.add("Center", this); + } + vncContainer = vncFrame; + } else { + vncContainer = this; + } + + recordingSync = new Object(); + + options = new OptionsFrame(this); + clipboard = new ClipboardFrame(this); + if (RecordingFrame.checkSecurity()) + rec = new RecordingFrame(this); + + sessionFileName = null; + recordingActive = false; + recordingStatusChanged = false; + cursorUpdatesDef = null; + eightBitColorsDef = null; + + if (inSeparateFrame) + vncFrame.addWindowListener(this); + + rfbThread = new Thread(this); + rfbThread.start(); + } + + public void update(Graphics g) { + } + + // + // run() - executed by the rfbThread to deal with the RFB socket. + // + + public void run() { + + gridbag = new GridBagLayout(); + vncContainer.setLayout(gridbag); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.anchor = GridBagConstraints.NORTHWEST; + + if (showControls) { + buttonPanel = new ButtonPanel(this); + gridbag.setConstraints(buttonPanel, gbc); + vncContainer.add(buttonPanel); + } + + + + + + /*****************************************************************************/ +// /* + vncFrame.pack(); + vncFrame.show(); + try{ + rfb = new RfbProto(host, port, this); + try { + rfb.readVersionMsg(); + rfb.writeVersionMsg(); + int secType = rfb.negotiateSecurity(); + }catch(Exception e){} + ServerSocket serverSocket = new ServerSocket(5550); + Socket socket = serverSocket.accept(); + rfb.chsock(socket); + + rfb.framebufferWidth = 1680; + rfb.framebufferHeight = 1050; + rfb.bitsPerPixel = 32; + rfb.depth = 32; + rfb.bigEndian = false; + rfb.trueColour = true; + rfb.redMax = 255; + rfb.greenMax = 255; + rfb.blueMax = 255; + rfb.redShift = 16; + rfb.greenShift = 8; + rfb.blueShift = 0; + rfb.inNormalProtocol = true; + + createCanvas(0, 0); + }catch(IOException e) {} + + gbc.weightx = 1.0; + gbc.weighty = 1.0; + + if (inSeparateFrame) { + + // Create a panel which itself is resizeable and can hold + // non-resizeable VncCanvas component at the top left corner. + Panel canvasPanel = new Panel(); + canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + canvasPanel.add(vc); + + // Create a ScrollPane which will hold a panel with VncCanvas + // inside. + desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED); + gbc.fill = GridBagConstraints.BOTH; + gridbag.setConstraints(desktopScrollPane, gbc); + desktopScrollPane.add(canvasPanel); + + // Finally, add our ScrollPane to the Frame window. + vncFrame.add(desktopScrollPane); + vncFrame.setTitle(rfb.desktopName); + vncFrame.pack(); + vc.resizeDesktopFrame(); + + } else { + + // Just add the VncCanvas component to the Applet. + gridbag.setConstraints(vc, gbc); + add(vc); + validate(); + + } +//*/ + /*****************************************************************************/ + +/* + + try { + connectAndAuthenticate(); + doProtocolInitialisation(); + + // FIXME: Use auto-scaling not only in a separate frame. + if (options.autoScale && inSeparateFrame) { + Dimension screenSize; + try { + screenSize = vncContainer.getToolkit().getScreenSize(); + } catch (Exception e) { + screenSize = new Dimension(0, 0); + } + createCanvas(screenSize.width - 32, screenSize.height - 32); + } else { + createCanvas(0, 0); + } + + gbc.weightx = 1.0; + gbc.weighty = 1.0; + + if (inSeparateFrame) { + + // Create a panel which itself is resizeable and can hold + // non-resizeable VncCanvas component at the top left corner. + Panel canvasPanel = new Panel(); + canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + canvasPanel.add(vc); + + // Create a ScrollPane which will hold a panel with VncCanvas + // inside. + desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED); + gbc.fill = GridBagConstraints.BOTH; + gridbag.setConstraints(desktopScrollPane, gbc); + desktopScrollPane.add(canvasPanel); + + // Finally, add our ScrollPane to the Frame window. + vncFrame.add(desktopScrollPane); + vncFrame.setTitle(rfb.desktopName); + vncFrame.pack(); + vc.resizeDesktopFrame(); + + } else { + + // Just add the VncCanvas component to the Applet. + gridbag.setConstraints(vc, gbc); + add(vc); + validate(); + + } +*/ + + try{ + + if (showControls) + buttonPanel.enableButtons(); + + moveFocusToDesktop(); + processNormalProtocol();//main loop + + } catch (NoRouteToHostException e) { + fatalError("Network error: no route to server: " + host, e); + } catch (UnknownHostException e) { + fatalError("Network error: server name unknown: " + host, e); + } catch (ConnectException e) { + fatalError("Network error: could not connect to server: " + + host + ":" + port, e); + } catch (EOFException e) { + if (showOfflineDesktop) { + e.printStackTrace(); + System.out.println("Network error: remote side closed connection"); + if (vc != null) { + vc.enableInput(false); + } + if (inSeparateFrame) { + vncFrame.setTitle(rfb.desktopName + " [disconnected]"); + } + if (rfb != null && !rfb.closed()) + rfb.close(); + if (showControls && buttonPanel != null) { + buttonPanel.disableButtonsOnDisconnect(); + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + } + } else { + fatalError("Network error: remote side closed connection", e); + } + } catch (IOException e) { + String str = e.getMessage(); + if (str != null && str.length() != 0) { + fatalError("Network Error: " + str, e); + } else { + fatalError(e.toString(), e); + } + } catch (Exception e) { + String str = e.getMessage(); + if (str != null && str.length() != 0) { + fatalError("Error: " + str, e); + } else { + fatalError(e.toString(), e); + } + } + + } + + // + // Create a VncCanvas instance. + // + + void createCanvas(int maxWidth, int maxHeight) throws IOException { + // Determine if Java 2D API is available and use a special + // version of VncCanvas if it is present. + vc = null; + try { + // This throws ClassNotFoundException if there is no Java 2D API. + Class cl = Class.forName("java.awt.Graphics2D"); + // If we could load Graphics2D class, then we can use VncCanvas2D. + cl = Class.forName("VncCanvas2"); + Class[] argClasses = { this.getClass(), Integer.TYPE, Integer.TYPE }; + java.lang.reflect.Constructor cstr = cl.getConstructor(argClasses); + Object[] argObjects = + { this, new Integer(maxWidth), new Integer(maxHeight) }; + vc = (VncCanvas)cstr.newInstance(argObjects); + } catch (Exception e) { + System.out.println("Warning: Java 2D API is not available"); + } + + // If we failed to create VncCanvas2D, use old VncCanvas. + if (vc == null) + vc = new VncCanvas(this, maxWidth, maxHeight); + } + + + // + // Process RFB socket messages. + // If the rfbThread is being stopped, ignore any exceptions, + // otherwise rethrow the exception so it can be handled. + // + + void processNormalProtocol() throws Exception { + try { + vc.processNormalProtocol();//main loop + } catch (Exception e) { + if (rfbThread == null) { + System.out.println("Ignoring RFB socket exceptions" + + " because applet is stopping"); + } else { + throw e; + } + } + } + + + // + // Connect to the RFB server and authenticate the user. + // + + void connectAndAuthenticate() throws Exception + { + showConnectionStatus("Initializing..."); + if (inSeparateFrame) { + vncFrame.pack(); + vncFrame.show(); + } else { + validate(); + } + + showConnectionStatus("Connecting to " + host + ", port " + port + "..."); + + rfb = new RfbProto(host, port, this); + showConnectionStatus("Connected to server"); + + rfb.readVersionMsg(); + showConnectionStatus("RFB server supports protocol version " + + rfb.serverMajor + "." + rfb.serverMinor); + + rfb.writeVersionMsg(); + showConnectionStatus("Using RFB protocol version " + + rfb.clientMajor + "." + rfb.clientMinor); + + int secType = rfb.negotiateSecurity(); + int authType; + if (secType == RfbProto.SecTypeTight) { + showConnectionStatus("Enabling TightVNC protocol extensions"); + rfb.setupTunneling(); + authType = rfb.negotiateAuthenticationTight(); + } else { + authType = secType; + } + + switch (authType) { + case RfbProto.AuthNone: + showConnectionStatus("No authentication needed"); + rfb.authenticateNone(); + break; + case RfbProto.AuthVNC: + showConnectionStatus("Performing standard VNC authentication"); + if (passwordParam != null) { + rfb.authenticateVNC(passwordParam); + } else { + String pw = askPassword(); + rfb.authenticateVNC(pw); + } + break; + default: + throw new Exception("Unknown authentication scheme " + authType); + } + } + + + // + // Show a message describing the connection status. + // To hide the connection status label, use (msg == null). + // + + void showConnectionStatus(String msg) + { + if (msg == null) { + if (vncContainer.isAncestorOf(connStatusLabel)) { + vncContainer.remove(connStatusLabel); + } + return; + } + + System.out.println(msg); + + if (connStatusLabel == null) { + connStatusLabel = new Label("Status: " + msg); + connStatusLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); + } else { + connStatusLabel.setText("Status: " + msg); + } + + if (!vncContainer.isAncestorOf(connStatusLabel)) { + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.insets = new Insets(20, 30, 20, 30); + gridbag.setConstraints(connStatusLabel, gbc); + vncContainer.add(connStatusLabel); + } + + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + } + + + // + // Show an authentication panel. + // + + String askPassword() throws Exception + { + showConnectionStatus(null); + + AuthPanel authPanel = new AuthPanel(this); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.anchor = GridBagConstraints.NORTHWEST; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.ipadx = 100; + gbc.ipady = 50; + gridbag.setConstraints(authPanel, gbc); + vncContainer.add(authPanel); + + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + + authPanel.moveFocusToDefaultField(); + String pw = authPanel.getPassword(); + vncContainer.remove(authPanel); + + return pw; + } + + + // + // Do the rest of the protocol initialisation. + // + + void doProtocolInitialisation() throws IOException + { + rfb.writeClientInit(); + rfb.readServerInit(); + + System.out.println("Desktop name is " + rfb.desktopName); + System.out.println("Desktop size is " + rfb.framebufferWidth + " x " + + rfb.framebufferHeight); + + setEncodings(); + + showConnectionStatus(null); + } + + + // + // Send current encoding list to the RFB server. + // + + int[] encodingsSaved; + int nEncodingsSaved; + + void setEncodings() { setEncodings(false); } + void autoSelectEncodings() { setEncodings(true); } + + void setEncodings(boolean autoSelectOnly) { + if (options == null || rfb == null || !rfb.inNormalProtocol) + return; + + int preferredEncoding = options.preferredEncoding; + if (preferredEncoding == -1) { + long kbitsPerSecond = rfb.kbitsPerSecond(); + if (nEncodingsSaved < 1) { + // Choose Tight or ZRLE encoding for the very first update. + System.out.println("Using Tight/ZRLE encodings"); + preferredEncoding = RfbProto.EncodingTight; + } else if (kbitsPerSecond > 2000 && + encodingsSaved[0] != RfbProto.EncodingHextile) { + // Switch to Hextile if the connection speed is above 2Mbps. + System.out.println("Throughput " + kbitsPerSecond + + " kbit/s - changing to Hextile encoding"); + preferredEncoding = RfbProto.EncodingHextile; + } else if (kbitsPerSecond < 1000 && + encodingsSaved[0] != RfbProto.EncodingTight) { + // Switch to Tight/ZRLE if the connection speed is below 1Mbps. + System.out.println("Throughput " + kbitsPerSecond + + " kbit/s - changing to Tight/ZRLE encodings"); + preferredEncoding = RfbProto.EncodingTight; + } else { + // Don't change the encoder. + if (autoSelectOnly) + return; + preferredEncoding = encodingsSaved[0]; + } + } else { + // Auto encoder selection is not enabled. + if (autoSelectOnly) + return; + } + + int[] encodings = new int[20]; + int nEncodings = 0; + + encodings[nEncodings++] = preferredEncoding; + if (options.useCopyRect) { + encodings[nEncodings++] = RfbProto.EncodingCopyRect; + } + + if (preferredEncoding != RfbProto.EncodingTight) { + encodings[nEncodings++] = RfbProto.EncodingTight; + } + if (preferredEncoding != RfbProto.EncodingZRLE) { + encodings[nEncodings++] = RfbProto.EncodingZRLE; + } + if (preferredEncoding != RfbProto.EncodingHextile) { + encodings[nEncodings++] = RfbProto.EncodingHextile; + } + if (preferredEncoding != RfbProto.EncodingZlib) { + encodings[nEncodings++] = RfbProto.EncodingZlib; + } + if (preferredEncoding != RfbProto.EncodingCoRRE) { + encodings[nEncodings++] = RfbProto.EncodingCoRRE; + } + if (preferredEncoding != RfbProto.EncodingRRE) { + encodings[nEncodings++] = RfbProto.EncodingRRE; + } + + if (options.compressLevel >= 0 && options.compressLevel <= 9) { + encodings[nEncodings++] = + RfbProto.EncodingCompressLevel0 + options.compressLevel; + } + if (options.jpegQuality >= 0 && options.jpegQuality <= 9) { + encodings[nEncodings++] = + RfbProto.EncodingQualityLevel0 + options.jpegQuality; + } + + if (options.requestCursorUpdates) { + encodings[nEncodings++] = RfbProto.EncodingXCursor; + encodings[nEncodings++] = RfbProto.EncodingRichCursor; + if (!options.ignoreCursorUpdates) + encodings[nEncodings++] = RfbProto.EncodingPointerPos; + } + + encodings[nEncodings++] = RfbProto.EncodingLastRect; + encodings[nEncodings++] = RfbProto.EncodingNewFBSize; + + boolean encodingsWereChanged = false; + if (nEncodings != nEncodingsSaved) { + encodingsWereChanged = true; + } else { + for (int i = 0; i < nEncodings; i++) { + if (encodings[i] != encodingsSaved[i]) { + encodingsWereChanged = true; + break; + } + } + } + + if (encodingsWereChanged) { + try { + rfb.writeSetEncodings(encodings, nEncodings); + if (vc != null) { + vc.softCursorFree(); + } + } catch (Exception e) { + e.printStackTrace(); + } + encodingsSaved = encodings; + nEncodingsSaved = nEncodings; + } + } + + + // + // setCutText() - send the given cut text to the RFB server. + // + + void setCutText(String text) { + try { + if (rfb != null && rfb.inNormalProtocol) { + rfb.writeClientCutText(text); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + // + // Order change in session recording status. To stop recording, pass + // null in place of the fname argument. + // + + void setRecordingStatus(String fname) { + synchronized(recordingSync) { + sessionFileName = fname; + recordingStatusChanged = true; + } + } + + // + // Start or stop session recording. Returns true if this method call + // causes recording of a new session. + // + + boolean checkRecordingStatus() throws IOException { + synchronized(recordingSync) { + if (recordingStatusChanged) { + recordingStatusChanged = false; + if (sessionFileName != null) { + startRecording(); + return true; + } else { + stopRecording(); + } + } + } + return false; + } + + // + // Start session recording. + // + + protected void startRecording() throws IOException { + synchronized(recordingSync) { + if (!recordingActive) { + // Save settings to restore them after recording the session. + cursorUpdatesDef = + options.choices[options.cursorUpdatesIndex].getSelectedItem(); + eightBitColorsDef = + options.choices[options.eightBitColorsIndex].getSelectedItem(); + // Set options to values suitable for recording. + options.choices[options.cursorUpdatesIndex].select("Disable"); + options.choices[options.cursorUpdatesIndex].setEnabled(false); + options.setEncodings(); + options.choices[options.eightBitColorsIndex].select("No"); + options.choices[options.eightBitColorsIndex].setEnabled(false); + options.setColorFormat(); + } else { + rfb.closeSession(); + } + + System.out.println("Recording the session in " + sessionFileName); + rfb.startSession(sessionFileName); + recordingActive = true; + } + } + + // + // Stop session recording. + // + + protected void stopRecording() throws IOException { + synchronized(recordingSync) { + if (recordingActive) { + // Restore options. + options.choices[options.cursorUpdatesIndex].select(cursorUpdatesDef); + options.choices[options.cursorUpdatesIndex].setEnabled(true); + options.setEncodings(); + options.choices[options.eightBitColorsIndex].select(eightBitColorsDef); + options.choices[options.eightBitColorsIndex].setEnabled(true); + options.setColorFormat(); + + rfb.closeSession(); + System.out.println("Session recording stopped."); + } + sessionFileName = null; + recordingActive = false; + } + } + + + // + // readParameters() - read parameters from the html source or from the + // command line. On the command line, the arguments are just a sequence of + // param_name/param_value pairs where the names and values correspond to + // those expected in the html applet tag source. + // + + void readParameters() { + // host = readParameter("HOST", !inAnApplet); + if(mainArgs.length > 0) host = mainArgs[0]; + else host = "hades.cr.ie.u-ryukyu.ac.jp"; + /* + if (host == null) { + host = getCodeBase().getHost(); + if (host.equals("")) { + fatalError("HOST parameter not specified"); + } + } + */ + + // port = readIntParameter("PORT", 5900); + if(mainArgs.length > 1) port = Integer.parseInt(mainArgs[1]); + else port = 5901; + // Read "ENCPASSWORD" or "PASSWORD" parameter if specified. + readPasswordParameters(); + + String str; + if (inAnApplet) { + str = readParameter("Open New Window", false); + if (str != null && str.equalsIgnoreCase("Yes")) + inSeparateFrame = true; + } + + // "Show Controls" set to "No" disables button panel. + showControls = true; + str = readParameter("Show Controls", false); + if (str != null && str.equalsIgnoreCase("No")) + showControls = false; + + // "Offer Relogin" set to "No" disables "Login again" and "Close + // window" buttons under error messages in applet mode. + offerRelogin = true; + str = readParameter("Offer Relogin", false); + if (str != null && str.equalsIgnoreCase("No")) + offerRelogin = false; + + // Do we continue showing desktop on remote disconnect? + showOfflineDesktop = false; + str = readParameter("Show Offline Desktop", false); + if (str != null && str.equalsIgnoreCase("Yes")) + showOfflineDesktop = true; + + // Fine tuning options. + deferScreenUpdates = readIntParameter("Defer screen updates", 20); + deferCursorUpdates = readIntParameter("Defer cursor updates", 10); + deferUpdateRequests = readIntParameter("Defer update requests", 0); + + // Debugging options. + debugStatsExcludeUpdates = readIntParameter("DEBUG_XU", 0); + debugStatsMeasureUpdates = readIntParameter("DEBUG_CU", 0); + + // SocketFactory. + socketFactory = readParameter("SocketFactory", false); + } + + // + // Read password parameters. If an "ENCPASSWORD" parameter is set, + // then decrypt the password into the passwordParam string. Otherwise, + // try to read the "PASSWORD" parameter directly to passwordParam. + // + + private void readPasswordParameters() { + String encPasswordParam = readParameter("ENCPASSWORD", false); + if (encPasswordParam == null) { + passwordParam = readParameter("PASSWORD", false); + + } else { + // ENCPASSWORD is hexascii-encoded. Decode. + byte[] pw = {0, 0, 0, 0, 0, 0, 0, 0}; + int len = encPasswordParam.length() / 2; + if (len > 8) + len = 8; + for (int i = 0; i < len; i++) { + String hex = encPasswordParam.substring(i*2, i*2+2); + Integer x = new Integer(Integer.parseInt(hex, 16)); + pw[i] = x.byteValue(); + } + // Decrypt the password. + byte[] key = {23, 82, 107, 6, 35, 78, 88, 7}; + DesCipher des = new DesCipher(key); + des.decrypt(pw, 0, pw, 0); + passwordParam = new String(pw); + + } + } + + public String readParameter(String name, boolean required) { + if (inAnApplet) { + String s = getParameter(name); + if ((s == null) && required) { + fatalError(name + " parameter not specified"); + } + return s; + } + + for (int i = 0; i < mainArgs.length; i += 2) { + if (mainArgs[i].equalsIgnoreCase(name)) { + try { + return mainArgs[i+1]; + } catch (Exception e) { + if (required) { + fatalError(name + " parameter not specified"); + } + return null; + } + } + } + if (required) { + fatalError(name + " parameter not specified"); + } + return null; + } + + int readIntParameter(String name, int defaultValue) { + String str = readParameter(name, false); + int result = defaultValue; + if (str != null) { + try { + result = Integer.parseInt(str); + } catch (NumberFormatException e) { } + } + return result; + } + + // + // moveFocusToDesktop() - move keyboard focus either to VncCanvas. + // + + void moveFocusToDesktop() { + if (vncContainer != null) { + if (vc != null && vncContainer.isAncestorOf(vc)) + vc.requestFocus(); + } + } + + // + // disconnect() - close connection to server. + // + + synchronized public void disconnect() { + System.out.println("Disconnecting"); + + if (vc != null) { + double sec = (System.currentTimeMillis() - vc.statStartTime) / 1000.0; + double rate = Math.round(vc.statNumUpdates / sec * 100) / 100.0; + int nRealRects = vc.statNumPixelRects; + int nPseudoRects = vc.statNumTotalRects - vc.statNumPixelRects; + System.out.println("Updates received: " + vc.statNumUpdates + " (" + + nRealRects + " rectangles + " + nPseudoRects + + " pseudo), " + rate + " updates/sec"); + int numRectsOther = nRealRects - vc.statNumRectsTight + - vc.statNumRectsZRLE - vc.statNumRectsHextile + - vc.statNumRectsRaw - vc.statNumRectsCopy; + System.out.println("Rectangles:" + + " Tight=" + vc.statNumRectsTight + + "(JPEG=" + vc.statNumRectsTightJPEG + + ") ZRLE=" + vc.statNumRectsZRLE + + " Hextile=" + vc.statNumRectsHextile + + " Raw=" + vc.statNumRectsRaw + + " CopyRect=" + vc.statNumRectsCopy + + " other=" + numRectsOther); + + int raw = vc.statNumBytesDecoded; + int compressed = vc.statNumBytesEncoded; + if (compressed > 0) { + double ratio = Math.round((double)raw / compressed * 1000) / 1000.0; + System.out.println("Pixel data: " + vc.statNumBytesDecoded + + " bytes, " + vc.statNumBytesEncoded + + " compressed, ratio " + ratio); + } + } + + if (rfb != null && !rfb.closed()) + rfb.close(); + options.dispose(); + clipboard.dispose(); + if (rec != null) + rec.dispose(); + + if (inAnApplet) { + showMessage("Disconnected"); + } else { + System.exit(0); + } + } + + // + // fatalError() - print out a fatal error message. + // FIXME: Do we really need two versions of the fatalError() method? + // + + synchronized public void fatalError(String str) { + System.out.println(str); + + if (inAnApplet) { + // vncContainer null, applet not inited, + // can not present the error to the user. + Thread.currentThread().stop(); + } else { + System.exit(1); + } + } + + synchronized public void fatalError(String str, Exception e) { + + if (rfb != null && rfb.closed()) { + // Not necessary to show error message if the error was caused + // by I/O problems after the rfb.close() method call. + System.out.println("RFB thread finished"); + return; + } + + System.out.println(str); + e.printStackTrace(); + + if (rfb != null) + rfb.close(); + + if (inAnApplet) { + showMessage(str); + } else { + System.exit(1); + } + } + + // + // Show message text and optionally "Relogin" and "Close" buttons. + // + + void showMessage(String msg) { + vncContainer.removeAll(); + + Label errLabel = new Label(msg, Label.CENTER); + errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); + + if (offerRelogin) { + + Panel gridPanel = new Panel(new GridLayout(0, 1)); + Panel outerPanel = new Panel(new FlowLayout(FlowLayout.LEFT)); + outerPanel.add(gridPanel); + vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 16)); + vncContainer.add(outerPanel); + Panel textPanel = new Panel(new FlowLayout(FlowLayout.CENTER)); + textPanel.add(errLabel); + gridPanel.add(textPanel); + gridPanel.add(new ReloginPanel(this)); + + } else { + + vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30)); + vncContainer.add(errLabel); + + } + + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + } + + // + // Stop the applet. + // Main applet thread will terminate on first exception + // after seeing that rfbThread has been set to null. + // + + public void stop() { + System.out.println("Stopping applet"); + rfbThread = null; + } + + // + // This method is called before the applet is destroyed. + // + + public void destroy() { + System.out.println("Destroying applet"); + + vncContainer.removeAll(); + options.dispose(); + clipboard.dispose(); + if (rec != null) + rec.dispose(); + if (rfb != null && !rfb.closed()) + rfb.close(); + if (inSeparateFrame) + vncFrame.dispose(); + } + + // + // Start/stop receiving mouse events. + // + + public void enableInput(boolean enable) { + vc.enableInput(enable); + } + + // + // Close application properly on window close event. + // + + public void windowClosing(WindowEvent evt) { + System.out.println("Closing window"); + if (rfb != null) + disconnect(); + + vncContainer.hide(); + + if (!inAnApplet) { + System.exit(0); + } + } + + // + // Ignore window events we're not interested in. + // + + public void windowActivated(WindowEvent evt) {} + public void windowDeactivated (WindowEvent evt) {} + public void windowOpened(WindowEvent evt) {} + public void windowClosed(WindowEvent evt) {} + public void windowIconified(WindowEvent evt) {} + public void windowDeiconified(WindowEvent evt) {} +} diff -r 000000000000 -r e04119c40b9b src/ZlibInStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ZlibInStream.java Tue Apr 12 12:57:33 2011 +0900 @@ -0,0 +1,111 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// +// A ZlibInStream reads from a zlib.io.InputStream +// + +public class ZlibInStream extends InStream { + + static final int defaultBufSize = 16384; + + public ZlibInStream(int bufSize_) { + bufSize = bufSize_; + b = new byte[bufSize]; + ptr = end = ptrOffset = 0; + inflater = new java.util.zip.Inflater(); + } + + public ZlibInStream() { this(defaultBufSize); } + + public void setUnderlying(InStream is, int bytesIn_) { + underlying = is; + bytesIn = bytesIn_; + ptr = end = 0; + } + + public void reset() throws Exception { + ptr = end = 0; + if (underlying == null) return; + + while (bytesIn > 0) { + decompress(); + end = 0; // throw away any data + } + underlying = null; + } + + public int pos() { return ptrOffset + ptr; } + + protected int overrun(int itemSize, int nItems) throws Exception { + if (itemSize > bufSize) + throw new Exception("ZlibInStream overrun: max itemSize exceeded"); + if (underlying == null) + throw new Exception("ZlibInStream overrun: no underlying stream"); + + if (end - ptr != 0) + System.arraycopy(b, ptr, b, 0, end - ptr); + + ptrOffset += ptr; + end -= ptr; + ptr = 0; + + while (end < itemSize) { + decompress(); + } + + if (itemSize * nItems > end) + nItems = end / itemSize; + + return nItems; + } + + // decompress() calls the decompressor once. Note that this won't + // necessarily generate any output data - it may just consume some input + // data. Returns false if wait is false and we would block on the underlying + // stream. + + private void decompress() throws Exception { + try { + underlying.check(1); + int avail_in = underlying.getend() - underlying.getptr(); + if (avail_in > bytesIn) + avail_in = bytesIn; + + if (inflater.needsInput()) { + inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in); + } + + int n = inflater.inflate(b, end, bufSize - end); + + end += n; + if (inflater.needsInput()) { + bytesIn -= avail_in; + underlying.setptr(underlying.getptr() + avail_in); + } + } catch (java.util.zip.DataFormatException e) { + throw new Exception("ZlibInStream: inflate failed"); + } + } + + private InStream underlying; + private int bufSize; + private int ptrOffset; + private java.util.zip.Inflater inflater; + private int bytesIn; +}