diff --git a/main/app_share/AuthPanel.class b/main/app_share/AuthPanel.class new file mode 100644 index 0000000000..a371e368f1 Binary files /dev/null and b/main/app_share/AuthPanel.class differ diff --git a/main/app_share/AuthPanel.java b/main/app_share/AuthPanel.java new file mode 100644 index 0000000000..679d433df0 --- /dev/null +++ b/main/app_share/AuthPanel.java @@ -0,0 +1,171 @@ +// +// 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.Button; +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.Panel; +import java.awt.TextField; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +// +// The panel which implements the user authentication scheme +// + +class AuthPanel extends Panel implements ActionListener { + + Label title, retry, prompt; + TextField password; + Button ok; + + // mslgon support + Label promptuser; + TextField username; + boolean mslogon = false; + // mslogon support end + + // + // Constructor. + // + + // mslgon support 2 + public AuthPanel(boolean logon) { + + mslogon = logon; + // mslgon support 2 end + + title = new Label("VNC Authentication",Label.CENTER); + title.setFont(new Font("Helvetica", Font.BOLD, 18)); + + prompt = new Label("Password:",Label.CENTER); + + password = new TextField(10); + password.setForeground(Color.black); + password.setBackground(Color.white); + password.setEchoChar('*'); + + // mslogon support 3 + + if (mslogon) { + promptuser = new Label("Username:",Label.CENTER); + username = new TextField(10); + username.setForeground(Color.black); + username.setBackground(Color.white); + } + // mslogon support 3 end + + ok = new Button("OK"); + + retry = new Label("",Label.CENTER); + retry.setFont(new Font("Courier", Font.BOLD, 16)); + + + GridBagLayout gridbag = new GridBagLayout(); + GridBagConstraints gbc = new GridBagConstraints(); + + setLayout(gridbag); + + gbc.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(title,gbc); + add(title); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gridbag.setConstraints(retry,gbc); + add(retry); + + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = 1; + + //mslogon support 4 + + if (mslogon) { + gridbag.setConstraints(promptuser,gbc); + add(promptuser); + gridbag.setConstraints(username,gbc); + add(username); + username.addActionListener(this); + } + //mslogon support 4 end + + gridbag.setConstraints(prompt,gbc); + add(prompt); + + gridbag.setConstraints(password,gbc); + add(password); + password.addActionListener(this); + + gbc.ipady = 10; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.fill = GridBagConstraints.BOTH; + gbc.insets = new Insets(0,20,0,0); + gbc.ipadx = 40; + gridbag.setConstraints(ok,gbc); + add(ok); + ok.addActionListener(this); + } + + // mslogon support 5 + public void setmslogon(boolean InfoMsLogon) { + mslogon = InfoMsLogon; + } + + public void moveFocusToUsernameField() { + if (mslogon) { username.requestFocus(); } + else { moveFocusToPasswordField();} + } + + // mslogon support 5 end + + // + // Move keyboard focus to the password text field object. + // + + public void moveFocusToPasswordField() { + password.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() == password || evt.getSource() == ok) { + password.setEnabled(false); + notify(); + } + } + + // + // retry(). + // + + public void retry() { + retry.setText("Sorry. Try again."); + password.setEnabled(true); + password.setText(""); + moveFocusToPasswordField(); + } + +} diff --git a/main/app_share/ButtonPanel.class b/main/app_share/ButtonPanel.class new file mode 100644 index 0000000000..4658f5d882 Binary files /dev/null and b/main/app_share/ButtonPanel.class differ diff --git a/main/app_share/ButtonPanel.java b/main/app_share/ButtonPanel.java new file mode 100644 index 0000000000..5837c4737c --- /dev/null +++ b/main/app_share/ButtonPanel.java @@ -0,0 +1,172 @@ +// Copyright (C) 2002-2003 Ultr@VNC Team. All Rights Reserved. +// Copyright (C) 2004 Kenn Min Chong, John Witchel. All Rights Reserved. +// 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; + Button ftpButton; + + 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); + ftpButton = new Button("File Transfer"); + ftpButton.setEnabled(false); + add(ftpButton); + ftpButton.addActionListener(this); + } + + // + // Enable buttons on successful connection. + // + + public void enableButtons() { + disconnectButton.setEnabled(true); + clipboardButton.setEnabled(true); + refreshButton.setEnabled(true); + ftpButton.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); + ftpButton.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(); + } + } + else if (evt.getSource() == ftpButton) + { + viewer.ftp.setVisible(!viewer.ftp.isVisible()); + viewer.rfb.readServerDriveList(); + + } + } +} + diff --git a/main/app_share/ChangeLog b/main/app_share/ChangeLog new file mode 100644 index 0000000000..7290585457 --- /dev/null +++ b/main/app_share/ChangeLog @@ -0,0 +1,44 @@ +2006-05 UltraVNC Team + * Improved MS Logon + * MS Logon weak challenge fixed + +2005-01-?? and 2005-02-?? ??:?? Ultr@VNC team + + * Made the viewer compilable with Java SDK 1.3 + * Added support for huge files transfers (> 4Gb) + * Added more color modes (64 colors, 8 Colors, Grey scales). Default settings + are now Tight and 64 Colors. + + +2004-11-10 23:00 Ultr@VNC Team + + * Prevented the FT GUI to be closed while a transfer is running + * Made FT GUI window non resizable + * Added confirmation dialogs for file Deletion and Overwrite + * Uniformized and cleaned the status/history messages + * Added "Stop" button + * Added certificate into the applet + * Made it compatible with enhanced FT protocole ( >= RC19) + * The selected pane is more "white" so the user knows to which + side the buttons apply + * Directory names are listed first, and are surrounded with + "[ " and " ]" + +2004-08-15 18:00 Ultr@VNC Team + + * Added Kenn Min Chong and John Witchel FileTransfer code and GUI + * Added improvements in FileTransfer code (compression...) and GUI + + +2004-07-05 12:00 Alban Chazot - Carmi Grenoble + + * Modified AuthPanel to show username if Ultr@VNC mslogon connection required + * Modified VncViewer to accept Ultravnc mslogon + * Modified VncViewer to add scrollPane to applet mode + +2002-09-30 12:00 Ultr@VNC Team + + * Replaced "TightVNC" with "Ultr@VNC" string in ClipboardFrame.java, + OptionsFrame.java and VncViewer.java + + * Added Ultr@VNC in the .vnc files diff --git a/main/app_share/ClipboardFrame.class b/main/app_share/ClipboardFrame.class new file mode 100644 index 0000000000..fc3e583ea2 Binary files /dev/null and b/main/app_share/ClipboardFrame.class differ diff --git a/main/app_share/ClipboardFrame.java b/main/app_share/ClipboardFrame.java new file mode 100644 index 0000000000..8998bcc42d --- /dev/null +++ b/main/app_share/ClipboardFrame.java @@ -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("Ultr@VNC 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 --git a/main/app_share/ConfigClientBean.class b/main/app_share/ConfigClientBean.class new file mode 100644 index 0000000000..20afa51863 Binary files /dev/null and b/main/app_share/ConfigClientBean.class differ diff --git a/main/app_share/ConfigClientBean.java b/main/app_share/ConfigClientBean.java new file mode 100644 index 0000000000..794ab71cef --- /dev/null +++ b/main/app_share/ConfigClientBean.java @@ -0,0 +1,14 @@ + +public class ConfigClientBean { + + boolean shareDesktop = true; + + public boolean isShareDesktop() { + return shareDesktop; + } + + public void setShareDesktop(boolean shareDesktop) { + this.shareDesktop = shareDesktop; + } + +} diff --git a/main/app_share/DH.class b/main/app_share/DH.class new file mode 100644 index 0000000000..bb315365d8 Binary files /dev/null and b/main/app_share/DH.class differ diff --git a/main/app_share/DH.java b/main/app_share/DH.java new file mode 100644 index 0000000000..a3500c99f5 --- /dev/null +++ b/main/app_share/DH.java @@ -0,0 +1,182 @@ +// CRYPTO LIBRARY FOR EXCHANGING KEYS +// USING THE DIFFIE-HELLMAN KEY EXCHANGE PROTOCOL + +// The diffie-hellman can be used to securely exchange keys +// between parties, where a third party eavesdropper given +// the values being transmitted cannot determine the key. + +// Implemented by Lee Griffiths, Jan 2004. +// This software is freeware, you may use it to your discretion, +// however by doing so you take full responsibility for any damage +// it may cause. + +// Hope you find it useful, even if you just use some of the functions +// out of it like the prime number generator and the XtoYmodN function. + +// It would be great if you could send me emails to: lee.griffiths@first4internet.co.uk +// with any suggestions, comments, or questions! + +// Enjoy. + +// Adopted to ms-logon for ultravnc and ported to Java by marscha, 2006. + +//import java.lang.Math; + +public class DH { + + public DH() { + maxNum = (((long) 1) << DH_MAX_BITS) - 1; + } + + public DH(long generator, long modulus) throws Exception { + maxNum = (((long) 1) << DH_MAX_BITS) - 1; + if (generator >= maxNum || modulus >= maxNum) + throw new Exception("Modulus or generator too large."); + gen = generator; + mod = modulus; + } + + private long rng(long limit) { + return (long) (java.lang.Math.random() * limit); + } + + //Performs the miller-rabin primality test on a guessed prime n. + //trials is the number of attempts to verify this, because the function + //is not 100% accurate it may be a composite. However setting the trial + //value to around 5 should guarantee success even with very large primes + private boolean millerRabin (long n, int trials) { + long a = 0; + + for (int i = 0; i < trials; i++) { + a = rng(n - 3) + 2;// gets random value in [2..n-1] + if (XpowYmodN(a, n - 1, n) != 1) return false; //n composite, return false + } + return true; // n probably prime + } + + //Generates a large prime number by + //choosing a randomly large integer, and ensuring the value is odd + //then uses the miller-rabin primality test on it to see if it is prime + //if not the value gets increased until it is prime + private long generatePrime() { + long prime = 0; + + do { + long start = rng(maxNum); + prime = tryToGeneratePrime(start); + } while (prime == 0); + return prime; + } + + private long tryToGeneratePrime(long prime) { + //ensure it is an odd number + if ((prime & 1) == 0) + prime += 1; + + long cnt = 0; + while (!millerRabin(prime, 25) && (cnt++ < DH_RANGE) && prime < maxNum) { + prime += 2; + if ((prime % 3) == 0) prime += 2; + } + return (cnt >= DH_RANGE || prime >= maxNum) ? 0 : prime; + } + + //Raises X to the power Y in modulus N + //the values of X, Y, and N can be massive, and this can be + //achieved by first calculating X to the power of 2 then + //using power chaining over modulus N + private long XpowYmodN(long x, long y, long N) { + long result = 1; + final long oneShift63 = ((long) 1) << 63; + + for (int i = 0; i < 64; y <<= 1, i++){ + result = result * result % N; + if ((y & oneShift63) != 0) + result = result * x % N; + } + return result; + } + + public void createKeys() { + gen = generatePrime(); + mod = generatePrime(); + + if (gen > mod) { + long swap = gen; + gen = mod; + mod = swap; + } + } + + public long createInterKey() { + priv = rng(maxNum); + return pub = XpowYmodN(gen,priv,mod); + } + + public long createEncryptionKey(long interKey) throws Exception { + if (interKey >= maxNum){ + throw new Exception("interKey too large"); + } + return key = XpowYmodN(interKey,priv,mod); + } + + + public long getValue(int flags) { + switch (flags) { + case DH_MOD: + return mod; + case DH_GEN: + return gen; + case DH_PRIV: + return priv; + case DH_PUB: + return pub; + case DH_KEY: + return key; + default: + return (long) 0; + } + } + + public int bits(long number){ + for (int i = 0; i < 64; i++){ + number /= 2; + if (number < 2) return i; + } + return 0; + } + + public static byte[] longToBytes(long number) { + byte[] bytes = new byte[8]; + for (int i = 0; i < 8; i++) { + bytes[i] = (byte) (0xff & (number >> (8 * (7 - i)))); + } + return bytes; + } + + public static long bytesToLong(byte[] bytes) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result += (byte) bytes[i]; + } + return result; + } + + private long gen; + private long mod; + private long priv; + private long pub; + private long key; + private long maxNum; + + private static final int DH_MAX_BITS = 31; + private static final int DH_RANGE = 100; + + private static final int DH_MOD = 1; + private static final int DH_GEN = 2; + private static final int DH_PRIV = 3; + private static final int DH_PUB = 4; + private static final int DH_KEY = 5; + +} \ No newline at end of file diff --git a/main/app_share/DesCipher.class b/main/app_share/DesCipher.class new file mode 100644 index 0000000000..09ca87ec45 Binary files /dev/null and b/main/app_share/DesCipher.class differ diff --git a/main/app_share/DesCipher.java b/main/app_share/DesCipher.java new file mode 100644 index 0000000000..b84fb7c3fe --- /dev/null +++ b/main/app_share/DesCipher.java @@ -0,0 +1,524 @@ +// +// 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 ); + } + + // Encrypt a text which is a multiple of 8 bytes. + public void encryptText( byte[] clearText, byte[] cipherText, byte[] key) { + int i, j; + + for (i = 0; i< 8; i++) + clearText[i] ^= key[i]; + encrypt(clearText, 0, cipherText, 0); + for (i = 8; i < clearText.length; i += 8) { + for (j = 0; j < 8; j++) + clearText[i + j] ^= cipherText[i + j - 8]; + encrypt(clearText, i, cipherText, i); + } + } + + // Decrypt a text which is a multiple of 8 bytes. + public void decryptText( byte[] cipherText, byte[] clearText, byte[] key) { + int i, j; + for (i = cipherText.length - 8; i > 0; i -= 8) { + decrypt(cipherText, i, clearText, i); + for (j = 0; j < 8; j++) + clearText[i + j] ^= cipherText[i + j - 8]; + } + /* i = 0 */ + decrypt(cipherText, 0, clearText, 0); + for (i = 0; i < 8; i++) + clearText[i] ^= key[i]; + } + + // 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 --git a/main/app_share/DokeosAppShare.exe b/main/app_share/DokeosAppShare.exe new file mode 100644 index 0000000000..a0b1f92851 Binary files /dev/null and b/main/app_share/DokeosAppShare.exe differ diff --git a/main/app_share/FTPFrame.class b/main/app_share/FTPFrame.class new file mode 100644 index 0000000000..e46ca35957 Binary files /dev/null and b/main/app_share/FTPFrame.class differ diff --git a/main/app_share/FTPFrame.java b/main/app_share/FTPFrame.java new file mode 100644 index 0000000000..f909cfd366 --- /dev/null +++ b/main/app_share/FTPFrame.java @@ -0,0 +1,1298 @@ +// Copyright (C) 2002-2005 Ultr@VNC Team. All Rights Reserved. +// Copyright (C) 2004 Kenn Min Chong, John Witchel. 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 javax.swing.JFrame; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.ArrayList; +import java.util.Vector; +import javax.swing.*; + + +/* + * Created on Feb 25, 2004 + * + */ + +/** + * @author John Witchel, Kenn Min Chong + * + */ +public class FTPFrame extends JFrame implements ActionListener, MouseListener { + VncViewer viewer; + + private javax.swing.JPanel jContentPane = null; + private javax.swing.JPanel topPanel = null; + private javax.swing.JPanel topPanelLocal = null; + private javax.swing.JPanel topPanelRemote = null; + private javax.swing.JPanel topPanelCenter = null; + private javax.swing.JPanel statusPanel = null; + private javax.swing.JPanel remotePanel = null; + private javax.swing.JPanel localPanel = null; + private javax.swing.JPanel buttonPanel = null; + private javax.swing.JButton sendButton = null; + private javax.swing.JButton receiveButton = null; + private javax.swing.JButton deleteButton = null; + private javax.swing.JButton newFolderButton = null; + private javax.swing.JButton stopButton = null; + private javax.swing.JButton closeButton = null; + private javax.swing.JButton dummyButton = null; + private javax.swing.JComboBox localDrivesComboBox = null; + private javax.swing.JComboBox remoteDrivesComboBox = null; + private javax.swing.JTextField localMachineLabel = null; + private javax.swing.JTextField remoteMachineLabel = null; + private javax.swing.JButton localTopButton = null; + private javax.swing.JButton remoteTopButton = null; + private javax.swing.JScrollPane localScrollPane = null; + private javax.swing.JList localFileTable = null; + private javax.swing.JScrollPane remoteScrollPane = null; + private javax.swing.JList remoteFileTable = null; + private javax.swing.JTextField remoteLocation = null; + private javax.swing.JTextField localLocation = null; + private javax.swing.JTextField localStatus = null; + public javax.swing.JTextField remoteStatus = null; + public javax.swing.JComboBox historyComboBox = null; + public javax.swing.JProgressBar jProgressBar = null; + public javax.swing.JTextField connectionStatus = null; + public boolean updateDriveList; + private Vector remoteList = null; + private Vector localList = null; + private File currentLocalDirectory = null; // Holds the current local Directory + private File currentRemoteDirectory = null; // Holds the current remote Directory + private File localSelection = null; // Holds the currently selected local file + private String remoteSelection = null; // Holds the currently selected remote file + public String selectedTable = null; + +// sf@2004 - Separate directories and files for better lisibility + private ArrayList DirsList; + private ArrayList FilesList; + + public static void main(String[] args) { + } + /** + * This is the default constructor + + public FTPFrame() { + super(); + initialize(); + } + */ + + /** + * This is Kenn's Constructor + * + */ + FTPFrame(VncViewer v) { + super("Ultr@VNC File Transfer"); + viewer = v; + // this.setUndecorated(true); // sf@2004 + this.setResizable(false); // sf@2004 + setSize(320, 240); + + // sf@2004 + DirsList = new ArrayList(); + FilesList = new ArrayList(); + + initialize(); + } + + /* Refreshing local and remote directory lists + * after an operation has been performed + */ + void refreshLocalLocation() + { + File f = new File(localLocation.getText()); + this.changeLocalDirectory(f); + } + + void refreshRemoteLocation() + { + remoteList.clear(); + remoteFileTable.setListData(remoteList); + viewer.rfb.readServerDirectory(remoteLocation.getText()); + } + + /* + * Prints the list of drives on the remote directory and returns a String[]. + * str takes as string like A:fC:lD:lE:lF:lG:cH:c + * in the form Drive Letter:Drive Type where + * f = floppy, l = local drive, c=CD-ROM, n = network + */ + String[] printDrives(String str) { + System.out.println(str); + updateDriveList = true; + remoteDrivesComboBox.removeAllItems(); + int size = str.length(); + String driveType = null; + String[] drive = new String[str.length() / 3]; + + // Loop through the string to create a String[] + for (int i = 0; i < size; i = i + 3) { + drive[i / 3] = str.substring(i, i + 2); + driveType = str.substring(i + 2, i + 3); + if (driveType.compareTo("f") == 0) + drive[i / 3] += "\\ Floppy"; + if (driveType.compareTo("l") == 0) + drive[i / 3] += "\\ Local Disk"; + if (driveType.compareTo("c") == 0) + drive[i / 3] += "\\ CD-ROM"; + if (driveType.compareTo("n") == 0) + drive[i / 3] += "\\ Network"; + + remoteDrivesComboBox.addItem(drive[i / 3]); + } + //sf@ - Select Drive C:as default if possible + boolean bFound = false; + for(int i = 0; i < remoteDrivesComboBox.getItemCount() ; i++) + { + if(remoteDrivesComboBox.getItemAt(i).toString().substring(0,1).toUpperCase().equals("C")) + { + remoteDrivesComboBox.setSelectedIndex(i); + bFound = true; + } + } + if (!bFound) remoteDrivesComboBox.setSelectedIndex(0); + updateDriveList = false; + return drive; + } + + /*Disable buttons/lists while file transfer is in progress*/ + + public void disableButtons() + { + closeButton.setEnabled(false); + deleteButton.setEnabled(false); + localTopButton.setEnabled(false); + newFolderButton.setEnabled(false); + stopButton.setVisible(true); + stopButton.setEnabled(true); + receiveButton.setEnabled(false); + remoteTopButton.setEnabled(false); + sendButton.setEnabled(false); + remoteFileTable.setEnabled(false); + localFileTable.setEnabled(false); + localLocation.setEnabled(false); + remoteLocation.setEnabled(false); + remoteDrivesComboBox.setEnabled(false); + localDrivesComboBox.setEnabled(false); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // sf@2004 + + } + /*Enable buttons/lists after file transfer is done*/ + + public void enableButtons() + { + closeButton.setEnabled(true); + deleteButton.setEnabled(true); + localTopButton.setEnabled(true); + newFolderButton.setEnabled(true); + stopButton.setVisible(false); + stopButton.setEnabled(false); + receiveButton.setEnabled(true); + remoteTopButton.setEnabled(true); + sendButton.setEnabled(true); + remoteFileTable.setEnabled(true); + localFileTable.setEnabled(true); + localLocation.setEnabled(true); + remoteLocation.setEnabled(true); + remoteDrivesComboBox.setEnabled(true); + localDrivesComboBox.setEnabled(true); + // setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); // sf@2004 + } + + /* + * Print Directory prints out all the contents of a directory + */ + void printDirectory(ArrayList a) { + + for (int i = 0; i < a.size(); i++) { + remoteList.addElement(a.get(i)); + } + remoteFileTable.setListData(remoteList); + } + + /** + * This method initializes this + * + * @return void + */ + private void initialize() { + this.setSize(794, 500); + this.setContentPane(getJContentPane()); + updateDriveList = true; + } + /** + * This method initializes jContentPane. This is the main content pane + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getJContentPane() { + if (jContentPane == null) { + jContentPane = new javax.swing.JPanel(); + jContentPane.setLayout(new java.awt.BorderLayout()); + jContentPane.add(getTopPanel(), java.awt.BorderLayout.NORTH); + jContentPane.add(getStatusPanel(), java.awt.BorderLayout.SOUTH); + jContentPane.add(getRemotePanel(), java.awt.BorderLayout.EAST); + jContentPane.add(getLocalPanel(), java.awt.BorderLayout.WEST); + jContentPane.add(getButtonPanel(), java.awt.BorderLayout.CENTER); + } + return jContentPane; + } + /** + * This method initializes topPanel + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getTopPanelLocal() { + if (topPanelLocal == null) { + topPanelLocal = new javax.swing.JPanel(); + topPanelLocal.setLayout(new java.awt.BorderLayout()); + topPanelLocal.setPreferredSize(new java.awt.Dimension(325, 22)); + topPanelLocal.add(getLocalDrivesComboBox(), java.awt.BorderLayout.WEST); + topPanelLocal.add(getLocalMachineLabel(), java.awt.BorderLayout.CENTER); + topPanelLocal.add(getLocalTopButton(), java.awt.BorderLayout.EAST); + topPanelLocal.setBackground(java.awt.Color.lightGray); + } + return topPanelLocal; + } + + /** + * This method initializes topPanelRemote + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getTopPanelRemote() { + if (topPanelRemote == null) { + topPanelRemote = new javax.swing.JPanel(); + topPanelRemote.setLayout(new java.awt.BorderLayout()); + topPanelRemote.setPreferredSize(new java.awt.Dimension(325, 20)); + topPanelRemote.add(getRemoteDrivesComboBox(), java.awt.BorderLayout.WEST); + topPanelRemote.add(getRemoteMachineLabel(), java.awt.BorderLayout.CENTER); + topPanelRemote.add(getRemoteTopButton(), java.awt.BorderLayout.EAST); + topPanelRemote.setBackground(java.awt.Color.lightGray); + } + return topPanelRemote; + } + + /** + * This method initializes topPanelRemote + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getTopPanelCenter() { + if (topPanelCenter == null) { + topPanelCenter = new javax.swing.JPanel(); + topPanelCenter.add(getDummyButton(), null); + } + return topPanelCenter; + } + + /** + * This method initializes topPanel + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getTopPanel() { + if (topPanel == null) { + topPanel = new javax.swing.JPanel(); + topPanel.setLayout(new java.awt.BorderLayout()); + //sf@2004 - We manage 2 top panels + topPanel.add(getTopPanelLocal(), java.awt.BorderLayout.WEST); + // topPanel.add(getTopPanelCenter(), java.awt.BorderLayout.CENTER); + topPanel.add(getTopPanelRemote(), java.awt.BorderLayout.EAST); + + /* + topPanel.add(getLocalDrivesComboBox(), null); + topPanel.add(getLocalMachineLabel(), null); + topPanel.add(getLocalTopButton(), null); + topPanel.add(getRemoteDrivesComboBox(), null); + topPanel.add(getRemoteMachineLabel(), null); + topPanel.add(getRemoteTopButton(), null); + topPanel.setBackground(java.awt.Color.lightGray); + */ + } + return topPanel; + } + + /** + * This method initializes statusPanel + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getStatusPanel() { + if (statusPanel == null) { + statusPanel = new javax.swing.JPanel(); + statusPanel.setLayout( + new javax.swing.BoxLayout( + statusPanel, + javax.swing.BoxLayout.Y_AXIS)); + statusPanel.add(getHistoryComboBox(), null); + statusPanel.add(getJProgressBar(), null); + statusPanel.add(getConnectionStatus(), null); + statusPanel.setBackground(java.awt.Color.lightGray); + + } + return statusPanel; + } + /** + * This method initializes remotePanel + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getRemotePanel() { + if (remotePanel == null) { + remotePanel = new javax.swing.JPanel(); + remotePanel.setLayout( + new javax.swing.BoxLayout( + remotePanel, + javax.swing.BoxLayout.Y_AXIS)); + remotePanel.add(getRemoteLocation(), null); + remotePanel.add(getRemoteScrollPane(), null); + remotePanel.add(getRemoteStatus(), null); + remotePanel.setBackground(java.awt.Color.lightGray); + } + return remotePanel; + } + /** + * This method initializes localPanel + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getLocalPanel() { + if (localPanel == null) { + localPanel = new javax.swing.JPanel(); + localPanel.setLayout( + new javax.swing.BoxLayout( + localPanel, + javax.swing.BoxLayout.Y_AXIS)); + localPanel.add(getLocalLocation(), null); + localPanel.add(getLocalScrollPane(), null); + localPanel.add(getLocalStatus(), null); + localPanel.setBackground(java.awt.Color.lightGray); + localPanel.setComponentOrientation( + java.awt.ComponentOrientation.UNKNOWN); + localPanel.setName("localPanel"); + } + return localPanel; + } + /** + * This method initializes buttonPanel + * + * @return javax.swing.JPanel + */ + private javax.swing.JPanel getButtonPanel() + { + if (buttonPanel == null) + { + buttonPanel = new javax.swing.JPanel(); + buttonPanel.setLayout(null); + buttonPanel.add(getReceiveButton(), null); + buttonPanel.add(getNewFolderButton(), null); + buttonPanel.add(getCloseButton(), null); + buttonPanel.add(getDeleteButton(), null); + buttonPanel.add(getSendButton(), null); + buttonPanel.add(getStopButton(), null); + buttonPanel.setBackground(java.awt.Color.lightGray); + } + return buttonPanel; + } + /** + * This method initializes sendButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getSendButton() { + if (sendButton == null) { + sendButton = new javax.swing.JButton(); + sendButton.setBounds(20, 30, 97, 25); + sendButton.setText("Send >>"); + sendButton.setName("sendButton"); + sendButton.addActionListener(this); + + } + return sendButton; + } + /** + * This method initializes receiveButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getReceiveButton() { + if (receiveButton == null) { + receiveButton = new javax.swing.JButton(); + receiveButton.setBounds(20, 60, 97, 25); + receiveButton.setText("<< Receive"); + receiveButton.setName("receiveButton"); + receiveButton.addActionListener(this); + } + return receiveButton; + } + /** + * This method initializes deleteButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getDeleteButton() { + if (deleteButton == null) { + deleteButton = new javax.swing.JButton(); + deleteButton.setBounds(20, 110, 97, 25); + deleteButton.setText("Delete File"); + deleteButton.setName("deleteButton"); + deleteButton.addActionListener(this); + } + return deleteButton; + } + /** + * This method initializes newFolderButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getNewFolderButton() { + if (newFolderButton == null) { + newFolderButton = new javax.swing.JButton(); + newFolderButton.setBounds(20, 140, 97, 25); + newFolderButton.setText("New Folder"); + newFolderButton.setName("newFolderButton"); + newFolderButton.addActionListener(this); + } + return newFolderButton; + } + + /** + * This method initializes stopButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getStopButton() + { + if (stopButton == null) + { + stopButton = new javax.swing.JButton(); + stopButton.setBounds(20, 200, 97, 25); + stopButton.setText("Stop"); + stopButton.setName("stopButton"); + stopButton.addActionListener(this); + stopButton.setVisible(false); + } + return stopButton; + } + + /** + * This method initializes closeButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getCloseButton() { + if (closeButton == null) { + closeButton = new javax.swing.JButton(); + closeButton.setBounds(20, 325, 97, 25); + closeButton.setText("Close"); + closeButton.setName("closeButton"); + closeButton.addActionListener(this); + } + return closeButton; + } + + /** + * This method initializes dummyButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getDummyButton() { + if (dummyButton == null) { + dummyButton = new javax.swing.JButton(); + dummyButton.setBounds(12, 206, 99, 25); + dummyButton.setText("aaaaaaaaaaaaaaa"); + dummyButton.setName("DummyButton"); + dummyButton.setVisible(false); + } + return dummyButton; + } + + /** + * This method initializes localDrivesComboBox + * + * @return javax.swing.JComboBox + */ + private javax.swing.JComboBox getLocalDrivesComboBox() { + updateDriveList = true; + // Read in Drive letters from local disk + File[] roots = File.listRoots(); + String[] localDisks = new String[roots.length]; + for (int i = 0; i < roots.length; i++) { + localDisks[i] = roots[i].toString(); + } + + // Create the combo box + if (localDrivesComboBox == null) { + localDrivesComboBox = new javax.swing.JComboBox(localDisks); + localDrivesComboBox.setName("LocalDisks"); + localDrivesComboBox.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + + //Select the second entry (e.g. C:\) + // localDrivesComboBox.setSelectedIndex(1); + localDrivesComboBox.addActionListener(this); + } + updateDriveList = false; + return localDrivesComboBox; + } + /** + * This method initializes remoteDrivesComboBox + * + * @return javax.swing.JComboBox + */ + public javax.swing.JComboBox getRemoteDrivesComboBox() { + if (remoteDrivesComboBox == null) { + remoteDrivesComboBox = new javax.swing.JComboBox(); + remoteDrivesComboBox.setName("remoteDisks"); + remoteDrivesComboBox.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + remoteDrivesComboBox.addActionListener(this); + + } + return remoteDrivesComboBox; + } + /** + * This method initializes localMachineLabel + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getLocalMachineLabel() { + if (localMachineLabel == null) { + localMachineLabel = new javax.swing.JTextField(); + localMachineLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + // localMachineLabel.setPreferredSize(new java.awt.Dimension(150, 19)); + localMachineLabel.setBackground(java.awt.Color.lightGray); + localMachineLabel.setText(" LOCAL MACHINE"); + localMachineLabel.setName("localLocation"); + localMachineLabel.setFont( + new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); + localMachineLabel.setEditable(false); + } + return localMachineLabel; + } + /** + * This method initializes remoteMachineLabel + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getRemoteMachineLabel() { + if (remoteMachineLabel == null) { + remoteMachineLabel = new javax.swing.JTextField(); + // remoteMachineLabel.setPreferredSize(new java.awt.Dimension(150, 19)); + remoteMachineLabel.setName("remoteLocation"); + remoteMachineLabel.setText(" REMOTE MACHINE"); + remoteMachineLabel.setBackground(java.awt.Color.lightGray); + remoteMachineLabel.setFont( + new java.awt.Font("Dialog", java.awt.Font.BOLD, 11)); + remoteMachineLabel.setEditable(false); + + } + return remoteMachineLabel; + } + /** + * This method initializes localTopButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getLocalTopButton() { + if (localTopButton == null) { + localTopButton = new javax.swing.JButton(); + localTopButton.setText("Root (\\)"); + // localTopButton.setPreferredSize(new java.awt.Dimension(30, 19)); + localTopButton.setFont( + new java.awt.Font("Dialog", java.awt.Font.BOLD, 10)); + localTopButton.addActionListener(this); + } + return localTopButton; + } + /** + * This method initializes remoteTopButton + * + * @return javax.swing.JButton + */ + private javax.swing.JButton getRemoteTopButton() { + if (remoteTopButton == null) { + remoteTopButton = new javax.swing.JButton(); + remoteTopButton.setText("Root (\\)"); + // remoteTopButton.setPreferredSize(new java.awt.Dimension(49, 25)); + remoteTopButton.setFont( + new java.awt.Font("Dialog", java.awt.Font.BOLD, 10)); + remoteTopButton.addActionListener(this); + } + return remoteTopButton; + } + /** + * This method initializes localFileTable + * + * @return javax.swing.JTable + */ + + private javax.swing.JList getLocalFileTable() { + if (localFileTable == null) { + localList = new Vector(0); + localFileTable = new JList(localList); + localFileTable.addMouseListener(this); + localFileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + return localFileTable; + } + /** + * This method initializes localScrollPane + * + * @return javax.swing.JScrollPane + */ + private javax.swing.JScrollPane getLocalScrollPane() { + if (localScrollPane == null) { + localScrollPane = new javax.swing.JScrollPane(); + localScrollPane.setViewportView(getLocalFileTable()); + localScrollPane.setPreferredSize(new java.awt.Dimension(325, 418)); + localScrollPane.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + localScrollPane.setName("localFileList"); + } + return localScrollPane; + } + /** + * This method initializes remoteFileTable + * + * @return javax.swing.JTable + */ + private javax.swing.JList getRemoteFileTable() { + if (remoteFileTable == null) { + remoteList = new Vector(0); + remoteFileTable = new JList(remoteList); + remoteFileTable.addMouseListener(this); + remoteFileTable.setSelectedValue("C:\\", false); + remoteFileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + } + return remoteFileTable; + } + /** + * This method initializes remoteScrollPane + * + * @return javax.swing.JScrollPane + */ + private javax.swing.JScrollPane getRemoteScrollPane() { + if (remoteScrollPane == null) { + remoteScrollPane = new javax.swing.JScrollPane(); + remoteScrollPane.setViewportView(getRemoteFileTable()); + remoteScrollPane.setPreferredSize(new java.awt.Dimension(325, 418)); + } + return remoteScrollPane; + } + /** + * This method initializes remoteLocation + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getRemoteLocation() + { + if (remoteLocation == null) + { + remoteLocation = new javax.swing.JTextField(); + remoteLocation.setText(""); + remoteLocation.setEditable(false); // sf@2004 + remoteLocation.setBackground(new Color(255,255,238)); + remoteLocation.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + } + return remoteLocation; + } + /** + * This method initializes localLocation + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getLocalLocation() { + if (localLocation == null) { + localLocation = new javax.swing.JTextField(); + localLocation.setText(""); + localLocation.setEditable(false); // sf@2004 + localLocation.setBackground( new Color(255,255,238)); + localLocation.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + } + return localLocation; + } + /** + * This method initializes localStatus + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getLocalStatus() { + if (localStatus == null) { + localStatus = new javax.swing.JTextField(); + // localStatus.setText("> Found 63 File(s) 7 Directorie(s)"); + localStatus.setBackground(java.awt.Color.lightGray); + localStatus.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + localStatus.setEditable(false); + } + return localStatus; + } + /** + * This method initializes remoteStatus + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getRemoteStatus() { + if (remoteStatus == null) { + remoteStatus = new javax.swing.JTextField(); + // remoteStatus.setText("> Found 15 File(s) 2 Directorie(s)"); + remoteStatus.setBackground(java.awt.Color.lightGray); + remoteStatus.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + remoteStatus.setEditable(false); + } + return remoteStatus; + } + /** + * This method initializes historyComboBox + * + * @return javax.swing.JComboBox + */ + private javax.swing.JComboBox getHistoryComboBox() { + if (historyComboBox == null) { + historyComboBox = new javax.swing.JComboBox(); + historyComboBox.setFont( + new java.awt.Font("Dialog", java.awt.Font.BOLD, 10)); + historyComboBox.insertItemAt(new String("Pulldown to view history ..."),0); + historyComboBox.setSelectedIndex(0); + historyComboBox.addActionListener(this); + } + return historyComboBox; + } + /** + * This method initializes jProgressBar + * + * @return javax.swing.JProgressBar + */ + private javax.swing.JProgressBar getJProgressBar() { + if (jProgressBar == null) { + jProgressBar = new javax.swing.JProgressBar(); + } + return jProgressBar; + } + /** + * This method initializes connectionStatus + * + * @return javax.swing.JTextField + */ + private javax.swing.JTextField getConnectionStatus() { + if (connectionStatus == null) { + connectionStatus = new javax.swing.JTextField(); + connectionStatus.setText("Connected..."); + connectionStatus.setBackground(java.awt.Color.lightGray); + connectionStatus.setFont( + new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10)); + } + connectionStatus.setEditable(false); + return connectionStatus; + } + + /** + * Implements Action listener. + */ + public void actionPerformed(ActionEvent evt) { + System.out.println(evt.getSource()); + + if (evt.getSource() == closeButton) + { // Close Button + doClose(); + } + else if (evt.getSource() == sendButton) + { + doSend(); + } + else if (evt.getSource() == receiveButton) + { + doReceive(); + } + else if (evt.getSource() == localDrivesComboBox) + { + changeLocalDrive(); + } + else if (evt.getSource() == remoteDrivesComboBox) + { + changeRemoteDrive(); + remoteList.clear(); + remoteFileTable.setListData(remoteList); + } + else if (evt.getSource() == localTopButton) + { + changeLocalDrive(); + } + else if (evt.getSource() == remoteTopButton) + { + changeRemoteDrive(); + } + else if(evt.getSource() == deleteButton) + { + doDelete(); + } + else if(evt.getSource()==newFolderButton) + { + doNewFolder(); + } + else if (evt.getSource() == stopButton) + { + doStop(); + } + + } + + private void doNewFolder() + { + String name = JOptionPane.showInputDialog(null,"Enter new directory name", "Create New Directory", JOptionPane.QUESTION_MESSAGE); + if(selectedTable.equals("remote")) + { + name = remoteLocation.getText()+name; + viewer.rfb.createRemoteDirectory(name); + } + else + { + name = localLocation.getText()+name; + File f = new File(name); + f.mkdir(); + refreshLocalLocation(); + historyComboBox.insertItemAt(new String("Created Local Directory: " + name),0); + historyComboBox.setSelectedIndex(0); + } + } + private void doClose() + { + try { + this.setVisible(false); + viewer.rfb.writeFramebufferUpdateRequest( + 0, + 0, + viewer.rfb.framebufferWidth, + viewer.rfb.framebufferHeight, + true); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void doDelete() + { + System.out.println("Delete Button Pressed"); + //Call this method to delete a file at server + if(selectedTable.equals("remote")) + { + String sFileName = ((String) this.remoteFileTable.getSelectedValue()); + +// sf@2004 - Directory can't be deleted + if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]")) + { + JOptionPane.showMessageDialog(null, (String)"Directory Deletion is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + + // for (int i = 0; i < remoteList.contains(size(); i++) + // remoteFileTable.g(i)); + // sf@2004 - Delete prompt + if (remoteList.contains(sFileName)) + { + int r = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete the file \n< " + sFileName + " >\n on Remote Machine ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION); + if (r == JOptionPane.NO_OPTION) + return; + } + + String fileName = remoteLocation.getText()+ sFileName.substring(1); + viewer.rfb.deleteRemoteFile(fileName); + } + else + { + String sFileName = ((String) this.localFileTable.getSelectedValue()); + +// sf@2004 - Directory can't be deleted + if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]")) + { + JOptionPane.showMessageDialog(null, (String)"Directory Deletion is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + // sf@2004 - Delete prompt + if (localList.contains(sFileName)) + { + int r = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete the file \n< " + sFileName + " >\n on Local Machine ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION); + if (r == JOptionPane.NO_OPTION) + return; + } + String s = localLocation.getText() + sFileName.substring(1); + File f = new File(s); + f.delete(); + refreshLocalLocation(); + historyComboBox.insertItemAt(new String("Deleted On Local Disk: " + s),0); + historyComboBox.setSelectedIndex(0); + } + } + + private void doReceive() + { + System.out.println("Received Button Pressed"); + + String sFileName = ((String) this.remoteFileTable.getSelectedValue()); + + // sf@2004 - Directory can't be transfered + if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]")) + { + JOptionPane.showMessageDialog(null, (String)"Directory Transfer is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + + // sf@2004 - Overwrite prompt + if (localList.contains(sFileName)) + { + int r = JOptionPane.showConfirmDialog(null, "The file < " + sFileName + " >\n already exists on Local Machine\n Are you sure you want to overwrite it ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION); + if (r == JOptionPane.NO_OPTION) + return; + } + + //updateHistory("Downloaded " + localSelection.toString()); + String remoteFileName = this.remoteLocation.getText(); + remoteFileName+= ((String) this.remoteFileTable.getSelectedValue()).substring(1); + + String localDestinationPath = this.localLocation.getText()+((String)this.remoteFileTable.getSelectedValue()).substring(1); + viewer.rfb.requestRemoteFile(remoteFileName,localDestinationPath); + } + + private void doSend() + { + System.out.println("Send Button Pressed"); + + String sFileName = ((String) this.localFileTable.getSelectedValue()); + + // sf@2004 - Directory can't be transfered + if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]")) + { + JOptionPane.showMessageDialog(null, (String)"Directory Transfer is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + + // sf@2004 - Overwrite prompt + if (remoteList.contains(sFileName)) + { + int r = JOptionPane.showConfirmDialog(null, "The file < " + sFileName + " >\n already exists on Remote Machine\n Are you sure you want to overwrite it ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION); + if (r == JOptionPane.NO_OPTION) + return; + } + //updateHistory("Uploaded " + localSelection.toString()); + String source = this.localLocation.getText(); + source += ((String) this.localFileTable.getSelectedValue()).substring(1); + + String destinationPath = this.remoteLocation.getText(); + + viewer.rfb.offerLocalFile(source,destinationPath); + } + + // + // sf@2004 - The user stops the current file transfer + // + private void doStop() + { + viewer.rfb.fAbort = true; + } + /** + * Update History: This method updates the history pulldown menu with the message string + * + */ + private void updateHistory(String message) + { + System.out.println("History: " + message); + historyComboBox.insertItemAt(new String(message), 0); + } + + /** + * This method updates the file table to the current selection of the remoteComboBox + * + */ + public void changeRemoteDrive() + { + remoteSelection = null; + + if (!updateDriveList) { + String drive = remoteDrivesComboBox.getSelectedItem().toString().substring(0,1)+ ":\\"; + viewer.rfb.readServerDirectory(drive); + remoteLocation.setText(drive); + } + remoteList.clear(); + remoteFileTable.setListData(remoteList); + } + /** + * changeLocalDrive updates the file table + * to the current selection of the localComboBox + */ + private void changeLocalDrive() + { + File currentDrive = new File(localDrivesComboBox.getSelectedItem().toString()); + if(currentDrive.canRead()) + { + localSelection = null; + localStatus.setText(""); + changeLocalDirectory(currentDrive); + } + else + { + localList.clear(); + localStatus.setText("WARNING: Drive " + localDrivesComboBox.getSelectedItem().toString()); + connectionStatus.setText(" > WARNING - Local Drive unavailable (possibly restricted access or media not present)"); + } + } + /** + * Determines which FileTable was double-clicked and updates the table + */ + public void mouseClicked(MouseEvent e) + { + + if(e.getClickCount() == 1) + { // Single clicked + if (e.getSource() == localFileTable ) + { // on local file table + updateLocalFileTableSelection(); + } + else if (e.getSource() == remoteFileTable) + { + updateRemoteFileTableSelection(); // on a remote file table + } + } + else if (e.getClickCount() == 2) + { // Mouse Double clicked + if (e.getSource() == localFileTable) + { // Clicked on local file + updateLocalFileTable(); + } + else if (e.getSource() == remoteFileTable) + { // Clicked on remote file + updateRemoteFileTable(); + } + } + } + /* + * Updates the globally accessible remote file selection if a file is single clicked in the RemoteFileTable + * + */ + private void updateRemoteFileTableSelection() { + selectedTable = "remote"; + localFileTable.setBackground(new Color(238, 238, 238)); + remoteFileTable.setBackground(new Color(255, 255, 255)); + String name = (remoteFileTable.getSelectedValue().toString()).substring(1); + if( !name.substring(0, 2).equals(" [")) + remoteSelection = remoteLocation.getText() + name.substring(0, name.length()); + + } + + /* + * Updates the globally accessible local file selection + * if a file is single clicked in the LocalFileTable + * + */ + private void updateLocalFileTableSelection() { + selectedTable="local"; + remoteFileTable.setBackground(new Color(238, 238, 238)); + localFileTable.setBackground(new Color(255, 255, 255)); + File currentSelection = new File(currentLocalDirectory, getTrimmedSelection()); + + if(currentSelection.isFile()) + localSelection = currentSelection.getAbsoluteFile(); + + } + /** + * Updates the Remote File Table based on selection. Called from mouseClicked handler + */ + public void updateRemoteFileTable() { + String name = null; + String action = null; + String drive = null; + name = (remoteFileTable.getSelectedValue().toString()).substring(1); + + if (name.equals("[..]")) + { + action = "up"; + remoteSelection = null; + drive = remoteLocation.getText().substring(0, remoteLocation.getText().length() - 1); + // JOptionPane.showMessageDialog(null, (String)drive, "FileTransfer DEBUG", JOptionPane.INFORMATION_MESSAGE); + int index = drive.lastIndexOf("\\"); + drive = drive.substring(0, index + 1); + + remoteLocation.setText(drive); + viewer.rfb.readServerDirectory(drive); + remoteList.clear(); + remoteFileTable.setListData(remoteList); + } + else if (!name.substring(0, 2).equals(" [") && !name.substring((name.length() - 1), name.length()).equals("]")) + { + action = "file"; + // Set the global remoteSelection field (used for get/put buttons) + remoteSelection = remoteLocation.getText() + name.substring(0, name.length()); + drive = remoteLocation.getText(); + // ?? + } + else + { + action = "down"; + remoteSelection = null; + name = name.substring(1, name.length() - 1); + drive = remoteLocation.getText() + name + "\\"; + remoteLocation.setText(drive); + viewer.rfb.readServerDirectory(drive); + remoteList.clear(); + remoteFileTable.setListData(remoteList); + } + //remoteLocation.setText(drive); + } + /** + * Updates the Local File Table based on selection. Called from MouseClicked handler + */ + + private void updateLocalFileTable() + { + localStatus.setText(""); + File currentSelection = new File(currentLocalDirectory , getTrimmedSelection()); // Selection + + if (getTrimmedSelection().equals("..")) + { // The [..] selected + localSelection = null; // No selection since directory changed + currentSelection = currentLocalDirectory.getParentFile(); + if(currentSelection != null) + { + changeLocalDirectory(currentSelection); + } + else + { + localStatus.setText("You are at the root !"); + } + } + else if (currentSelection.isFile()) + { + localSelection = currentSelection.getAbsoluteFile(); + } + else if (currentSelection.isDirectory()) + { + localSelection = null; // No selection since directory changed + changeLocalDirectory(currentSelection); + } + } + + /* + * Trims off the [] of a directory entry if it exists, else ignores it + * + */ + private String getTrimmedSelection(){ + String currentSelection = (localFileTable.getSelectedValue().toString()).substring(1); + if(currentSelection.substring(0,1).equals("[") && + currentSelection.substring(currentSelection.length()-1,currentSelection.length()).equals("]")){ + return currentSelection.substring(1,currentSelection.length()-1); + } else { + return currentSelection; + } + } + + /* + * Reads the localDriveComboBox and returns the first readable drive for populating + * the file table on load, so it's not looking at the A:\ drive when it opens. + */ + public File getFirstReadableLocalDrive(){ + File currentDrive; + // sf@ - Select C: as default first readable drive + for(int i = 0; i < localDrivesComboBox.getItemCount() ; i++) + { + currentDrive = new File(localDrivesComboBox.getItemAt(i).toString()); + if(localDrivesComboBox.getItemAt(i).toString().substring(0,1).toUpperCase().equals("C") && currentDrive.canRead()) + { + localDrivesComboBox.setSelectedIndex(i); + return currentDrive; + } + } + // if C: not available, take the first readable drive, this time. + for(int i = 0; i < localDrivesComboBox.getItemCount() ; i++) + { + currentDrive = new File(localDrivesComboBox.getItemAt(i).toString()); + if(currentDrive.canRead()) + { + localDrivesComboBox.setSelectedIndex(i); + return currentDrive; + } + } + + localStatus.setText("ERROR!: No Local Drives are Readable"); + return null; + } + + + /* + * Navigates the local file structure up or down one directory + */ + public void changeLocalDirectory(File dir) + { + currentLocalDirectory = dir; // Updates Global + File allFiles[] = dir.listFiles(); // Reads files + String[] contents = dir.list(); + + localList.clear(); + localList.addElement(" [..]"); + + // Populate the Lists + for (int i = 0; i < contents.length; i++) + { + if (allFiles[i].isDirectory()) + // localList.addElement("[" + contents[i] + "]"); + DirsList.add(" [" + contents[i] + "]"); // sf@2004 + else + { + // localList.addElement(contents[i]); + FilesList.add(" " + contents[i]); // sf@2004 + } + } + // sf@2004 + for (int i = 0; i < DirsList.size(); i++) + localList.addElement(DirsList.get(i)); + for (int i = 0; i < FilesList.size(); i++) + localList.addElement(FilesList.get(i)); + + FilesList.clear(); + DirsList.clear(); + + localFileTable.setListData(localList); + if(dir.toString().charAt(dir.toString().length()-1)==(File.separatorChar)) + { + localLocation.setText(dir.toString()); + } + else + { + localLocation.setText(dir.toString()+File.separator); // Display updated location above file table + } + localStatus.setText("Total Files / Folders: " + (localList.size()-1)); + } + public void mouseEntered(MouseEvent e) { + } + public void mouseExited(MouseEvent e) { + } + public void mousePressed(MouseEvent e) { + } + public void mouseReleased(MouseEvent e) { + } + +} // @jve:visual-info decl-index=0 visual-constraint="10,10" diff --git a/main/app_share/IProtocolCommand.class b/main/app_share/IProtocolCommand.class new file mode 100644 index 0000000000..25cd55bc95 Binary files /dev/null and b/main/app_share/IProtocolCommand.class differ diff --git a/main/app_share/IProtocolCommand.java b/main/app_share/IProtocolCommand.java new file mode 100644 index 0000000000..d9c39aea96 --- /dev/null +++ b/main/app_share/IProtocolCommand.java @@ -0,0 +1,18 @@ + + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public interface IProtocolCommand { + + public static final int SERVER_CONNECTION = 10; + public static final int CLIENT_CONNECTION = 20; + + public static final int RELAY_CONNECTION = 30; + + /*public Set ALL_COMMANDS = new HashSet(Arrays.asList(new Integer[] { new Integer(SERVER_CONNECTION), + new Integer(CLIENT_CONNECTION), + new Integer(RELAY_CONNECTION)})); + */ +} diff --git a/main/app_share/LICENCE.TXT b/main/app_share/LICENCE.TXT new file mode 100644 index 0000000000..ae3b53193c --- /dev/null +++ b/main/app_share/LICENCE.TXT @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/main/app_share/Makefile b/main/app_share/Makefile new file mode 100644 index 0000000000..a87cf03273 --- /dev/null +++ b/main/app_share/Makefile @@ -0,0 +1,35 @@ +# +# Making the VNC applet. +# + +CP = cp +JC = javac +JAR = jar +ARCHIVE = VncViewer.jar +PAGES = index.vnc shared.vnc noshared.vnc hextile.vnc zlib.vnc tight.vnc +INSTALL_DIR = /usr/local/vnc/classes + +CLASSES = VncViewer.class RfbProto.class AuthPanel.class VncCanvas.class \ + OptionsFrame.class ClipboardFrame.class ButtonPanel.class \ + DesCipher.class RecordingFrame.class SessionRecorder.class DH.class + +SOURCES = VncViewer.java RfbProto.java AuthPanel.java VncCanvas.java \ + OptionsFrame.java ClipboardFrame.java ButtonPanel.java \ + DesCipher.java RecordingFrame.java SessionRecorder.java DH.java + +all: $(CLASSES) $(ARCHIVE) + +$(CLASSES): $(SOURCES) + $(JC) -O $(SOURCES) + +$(ARCHIVE): $(CLASSES) + $(JAR) cf $(ARCHIVE) $(CLASSES) + +install: $(CLASSES) $(ARCHIVE) + $(CP) $(CLASSES) $(ARCHIVE) $(PAGES) $(INSTALL_DIR) + +export:: $(CLASSES) $(ARCHIVE) $(PAGES) + @$(ExportJavaClasses) + +clean:: + $(RM) *.class *.jar diff --git a/main/app_share/NoctisRfbProto.class b/main/app_share/NoctisRfbProto.class new file mode 100644 index 0000000000..f64dd7d5b9 Binary files /dev/null and b/main/app_share/NoctisRfbProto.class differ diff --git a/main/app_share/NoctisRfbProto.java b/main/app_share/NoctisRfbProto.java new file mode 100644 index 0000000000..7ea3eb544d --- /dev/null +++ b/main/app_share/NoctisRfbProto.java @@ -0,0 +1,1247 @@ +// Copyright (C) 2002-2004 Ultr@VNC Team. All Rights Reserved. +// Copyright (C) 2004 Kenn Min Chong, John Witchel. All Rights Reserved. +// Copyright (C) 2001,2002 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. +// + +// +// RfbProto.java +//4/19/04 + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.util.ArrayList; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import javax.swing.JOptionPane; + +class NoctisRfbProto { + + final String versionMsg = "RFB 003.003\n"; + + final static int ConnFailed = 0, NoAuth = 1, VncAuth = 2, MsLogon = 0xfffffffa; + + final static int VncAuthOK = 0, VncAuthFailed = 1, VncAuthTooMany = 2; + + final static int FramebufferUpdate = 0, SetColourMapEntries = 1, Bell = 2, ServerCutText = 3, rfbFileTransfer = 7; + + final int SetPixelFormat = 0, FixColourMapEntries = 1, SetEncodings = 2, FramebufferUpdateRequest = 3, KeyboardEvent = 4, PointerEvent = 5, ClientCutText = 6; + + final static int EncodingRaw = 0, EncodingCopyRect = 1, EncodingRRE = 2, EncodingCoRRE = 4, EncodingHextile = 5, EncodingZlib = 6, EncodingTight = 7, EncodingCompressLevel0 = 0xFFFFFF00, + EncodingQualityLevel0 = 0xFFFFFFE0, EncodingXCursor = 0xFFFFFF10, EncodingRichCursor = 0xFFFFFF11, EncodingPointerPos = 0xFFFFFF18, // marscha - PointerPos + EncodingLastRect = 0xFFFFFF20, EncodingNewFBSize = 0xFFFFFF21; + + final int HextileRaw = (1 << 0); + + final int HextileBackgroundSpecified = (1 << 1); + + final int HextileForegroundSpecified = (1 << 2); + + final int HextileAnySubrects = (1 << 3); + + final int HextileSubrectsColoured = (1 << 4); + + final static int TightExplicitFilter = 0x04; + + final static int TightFill = 0x08; + + final static int TightJpeg = 0x09; + + final static int TightMaxSubencoding = 0x09; + + final static int TightFilterCopy = 0x00; + + final static int TightFilterPalette = 0x01; + + final static int TightFilterGradient = 0x02; + + final static int TightMinToCompress = 12; + + // sf@2004 - FileTransfer part + ArrayList remoteDirsList; + + ArrayList remoteFilesList; + + ArrayList a; + + boolean fFTInit = true; // sf@2004 + + boolean fFTAllowed = true; + + boolean fAbort = false; + + boolean fFileReceptionError = false; + + boolean fFileReceptionRunning = false; + + boolean inDirectory2; + + FileOutputStream fos; + + FileInputStream fis; + + String sendFileSource; + + String receivePath; + + long fileSize; + + long receiveFileSize; + + long fileChunkCounter; + + final static int sz_rfbFileTransferMsg = 12, + // FileTransfer Content types and Params defines + rfbDirContentRequest = 1, + // Client asks for the content of a given Server directory + rfbDirPacket = 2, // Full directory name or full file name. + // Null content means end of Directory + rfbFileTransferRequest = 3, + // Client asks the server for the tranfer of a given file + rfbFileHeader = 4, + // First packet of a file transfer, containing file's features + rfbFilePacket = 5, // One slice of the file + rfbEndOfFile = 6, + // End of file transfer (the file has been received or error) + rfbAbortFileTransfer = 7, + // The file transfer must be aborted, whatever the state + rfbFileTransferOffer = 8, + // The client offers to send a file to the server + rfbFileAcceptHeader = 9, // The server accepts or rejects the file + rfbCommand = 10, + // The Client sends a simple command (File Delete, Dir create etc...) + rfbCommandReturn = 11, + // New FT Protocole (V2) The zipped checksums of the destination file (Delta Transfer) + rfbFileChecksums = 12, + // The Client receives the server's answer about a simple command + // rfbDirContentRequest client Request - content params + rfbRDirContent = 1, // Request a Server Directory contents + rfbRDrivesList = 2, // Request the server's drives list + + // rfbDirPacket & rfbCommandReturn server Answer - content params + rfbADirectory = 1, // Reception of a directory name + rfbAFile = 2, // Reception of a file name + rfbADrivesList = 3, // Reception of a list of drives + rfbADirCreate = 4, // Response to a create dir command + rfbADirDelete = 5, // Response to a delete dir command + rfbAFileCreate = 6, // Response to a create file command + rfbAFileDelete = 7, // Response to a delete file command + + // rfbCommand Command - content params + rfbCDirCreate = 1, // Request the server to create the given directory + rfbCDirDelete = 2, // Request the server to delete the given directory + rfbCFileCreate = 3, // Request the server to create the given file + rfbCFileDelete = 4, // Request the server to delete the given file + + // Errors - content params or "size" field + rfbRErrorUnknownCmd = 1, // Unknown FileTransfer command. + rfbRErrorCmd = 0xFFFFFFFF, + + // Error when a command fails on remote side (ret in "size" field) + sz_rfbBlockSize = 8192, // new FT protocole (v2) + + // Size of a File Transfer packet (before compression) + sz_rfbZipDirectoryPrefix = 9; + + String rfbZipDirectoryPrefix = "!UVNCDIR-\0"; + + // Transfered directory are zipped in a file with this prefix. Must end with "-" + + // End of FileTransfer part + + String host; + + int port; + + Socket sock; + + DataInputStream is; + + OutputStream os; + + OutputStreamWriter osw; + + SessionRecorder rec; + + boolean inNormalProtocol = false; + + // VncViewer viewer; + ConfigClientBean config; + + // 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- 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- 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; + + // + // Constructor. Make TCP connection to RFB server. + // + + NoctisRfbProto(String h, int p, ConfigClientBean c) throws IOException { + config = c; + host = h; + port = p; + sock = new Socket(host, port); + is = new DataInputStream(new BufferedInputStream(sock.getInputStream(), 16384)); + os = sock.getOutputStream(); + osw = new OutputStreamWriter(sock.getOutputStream()); + inDirectory2 = false; + a = new ArrayList(); + // sf@2004 + remoteDirsList = new ArrayList(); + remoteFilesList = new ArrayList(); + + sendFileSource = ""; + } + + void close() { + try { + sock.close(); + if (rec != null) { + rec.close(); + rec = null; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // + // Read server's protocol version message + // + + int serverMajor, serverMinor; + + void readVersionMsg() throws Exception { + + byte[] b = new byte[12]; + + is.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'); + } + + // + // Write our protocol version message + // + + void writeVersionMsg() throws IOException { + os.write(versionMsg.getBytes()); + } + + // + // Find out the authentication scheme. + // + + int readAuthScheme() throws Exception { + int authScheme = is.readInt(); + + switch (authScheme) { + + case ConnFailed: + int reasonLen = is.readInt(); + byte[] reason = new byte[reasonLen]; + is.readFully(reason); + throw new Exception(new String(reason)); + + case NoAuth: + case VncAuth: + case MsLogon: + return authScheme; + + default: + throw new Exception("Unknown authentication scheme from RFB server: " + authScheme); + + } + } + + // + // Write the client initialisation message + // + + void writeClientInit() throws IOException { + if (config.isShareDesktop()) { + os.write(1); + } else { + os.write(0); + } + config.setShareDesktop(false); + } + + // + // 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 = is.readUnsignedShort(); + framebufferHeight = is.readUnsignedShort(); + bitsPerPixel = is.readUnsignedByte(); + depth = is.readUnsignedByte(); + bigEndian = (is.readUnsignedByte() != 0); + trueColour = (is.readUnsignedByte() != 0); + redMax = is.readUnsignedShort(); + greenMax = is.readUnsignedShort(); + blueMax = is.readUnsignedShort(); + redShift = is.readUnsignedByte(); + greenShift = is.readUnsignedByte(); + blueShift = is.readUnsignedByte(); + byte[] pad = new byte[3]; + is.readFully(pad); + int nameLength = is.readInt(); + byte[] name = new byte[nameLength]; + is.readFully(name); + desktopName = new String(name); + + 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.getBytes()); + rec.writeIntBE(NoAuth); + 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; + + 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 = is.readUnsignedByte(); + + // 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 { + is.readByte(); + updateNRects = is.readUnsignedShort(); + + // 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 = is.readUnsignedShort(); + updateRectY = is.readUnsignedShort(); + updateRectW = is.readUnsignedShort(); + updateRectH = is.readUnsignedShort(); + updateRectEncoding = is.readInt(); + + if (updateRectEncoding == EncodingZlib || 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 == EncodingLastRect || updateRectEncoding == EncodingNewFBSize) + 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 = is.readUnsignedShort(); + copyRectSrcY = is.readUnsignedShort(); + + // If the session is being recorded: + if (rec != null) { + rec.writeShortBE(copyRectSrcX); + rec.writeShortBE(copyRectSrcY); + } + } + + // + // Read a ServerCutText message + // + + String readServerCutText() throws IOException { + byte[] pad = new byte[3]; + is.readFully(pad); + int len = is.readInt(); + byte[] text = new byte[len]; + is.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] = is.readUnsignedByte(); + int byteCount = 1; + int len = portion[0] & 0x7F; + if ((portion[0] & 0x80) != 0) { + portion[1] = is.readUnsignedByte(); + byteCount++; + len |= (portion[1] & 0x7F) << 7; + if ((portion[1] & 0x80) != 0) { + portion[2] = is.readUnsignedByte(); + byteCount++; + len |= (portion[2] & 0xFF) << 14; + } + } + + if (rec != null && recordFromBeginning) + for (int i = 0; i < byteCount; i++) + rec.writeByte(portion[i]); + + return len; + } + + // Author: Kenn Min Chong///////////////////////////////////////////// + + // Read/Write a rfbFileTransferMsg + /* + * typedef struct _rfbFileTransferMsg { CARD8 type; // always rfbFileTransfer CARD8 contentType; // See defines below CARD16 contentParam;// Other possible content classification (Dir or File + * name, etc..) CARD32 size; // FileSize or packet index or error or other CARD32 length; // followed by data char text[length] } rfbFileTransferMsg; + */ + + // Parsing Rfb message to see what type + void readRfbFileTransferMsg() throws IOException { + int contentType = is.readUnsignedByte(); + int contentParamT = is.readUnsignedByte(); + int contentParam = contentParamT; + contentParamT = is.readUnsignedByte(); + contentParamT = contentParamT << 8; + contentParam = contentParam | contentParamT; + if (contentType == rfbRDrivesList || contentType == rfbDirPacket) { + readDriveOrDirectory(contentParam); + } else if (contentType == rfbFileHeader) { + receiveFileHeader(); + } else if (contentType == rfbFilePacket) { + receiveFileChunk(); + } else if (contentType == rfbEndOfFile) { + endOfReceiveFile(true); // Ok + } else if (contentType == rfbAbortFileTransfer) { + if (fFileReceptionRunning) { + endOfReceiveFile(false); // Error + } else { + // sf@2004 - Todo: Add TestPermission + // System.out.println("File Transfer Aborted!"); + } + + } else if (contentType == rfbFileChecksums) { + ReceiveDestinationFileChecksums(); + } else { + System.out.println("ContentType: " + contentType); + } + } + + // Refactored from readRfbFileTransferMsg() + public void readDriveOrDirectory(int contentParam) throws IOException { + if (contentParam == rfbADrivesList) { + readFTPMsgDriveList(); + } else if (contentParam == rfbADirectory && !inDirectory2) { + inDirectory2 = true; + readFTPMsgDirectoryList(); + } else if (contentParam == rfbADirectory && inDirectory2) { + readFTPMsgDirectoryListContent(); + } else if (contentParam == 0) { + readFTPMsgDirectoryListEndContent(); + inDirectory2 = false; + } else { + System.out.println("ContentParam: " + contentParam); + } + } + + // Internally used. Write an Rfb message to the server + void writeRfbFileTransferMsg(int contentType, int contentParam, long size, // 0 : compression not supported - 1 : compression supported + long length, String text) throws IOException { + byte b[] = new byte[12]; + + b[0] = (byte) rfbFileTransfer; + b[1] = (byte) contentType; + b[2] = (byte) contentParam; + + byte by = 0; + long c = 0; + length++; + c = size & 0xFF000000; + by = (byte) (c >>> 24); + b[4] = by; + c = size & 0xFF0000; + by = (byte) (c >>> 16); + b[5] = by; + c = size & 0xFF00; + by = (byte) (c >>> 8); + b[6] = by; + c = size & 0xFF; + by = (byte) c; + b[7] = by; + + c = length & 0xFF000000; + by = (byte) (c >>> 24); + b[8] = by; + c = length & 0xFF0000; + by = (byte) (c >>> 16); + b[9] = by; + c = length & 0xFF00; + by = (byte) (c >>> 8); + b[10] = by; + c = length & 0xFF; + by = (byte) c; + b[11] = by; + os.write(b); + + if (text != null) { + byte byteArray[] = text.getBytes(); + byte byteArray2[] = new byte[byteArray.length + 1]; + for (int i = 0; i < byteArray.length; i++) { + byteArray2[i] = byteArray[i]; + } + byteArray2[byteArray2.length - 1] = 0; + os.write(byteArray2); + } + + } + + // Call this method to send a file from local pc to server + void offerLocalFile(String source, String destinationPath) { + try { + sendFileSource = source; + File f = new File(source); + // sf@2004 - Add support for huge files + long lSize = f.length(); + int iLowSize = (int) (lSize & 0x00000000FFFFFFFF); + int iHighSize = (int) (lSize >> 32); + + String temp = destinationPath + f.getName(); + writeRfbFileTransferMsg(rfbFileTransferOffer, 0, iLowSize, // f.length(), + temp.length(), temp); + + // sf@2004 - Send the high part of the size + byte b[] = new byte[4]; + byte by = 0; + long c = 0; + c = iHighSize & 0xFF000000; + by = (byte) (c >>> 24); + b[0] = by; + c = iHighSize & 0xFF0000; + by = (byte) (c >>> 16); + b[1] = by; + c = iHighSize & 0xFF00; + by = (byte) (c >>> 8); + b[2] = by; + c = iHighSize & 0xFF; + by = (byte) c; + b[3] = by; + os.write(b); + } catch (IOException e) { + System.err.println(e); + } + } + + // Call this method to delete a file at server + void deleteRemoteFile(String text) { + try { + String temp = text; + writeRfbFileTransferMsg(rfbCommand, rfbCFileDelete, 0, temp.length(), temp); + } catch (IOException e) { + System.err.println(e); + } + } + + // Call this method to create a directory at server + void createRemoteDirectory(String text) { + try { + String temp = text; + writeRfbFileTransferMsg(rfbCommand, rfbCDirCreate, 0, temp.length(), temp); + } catch (IOException e) { + System.err.println(e); + } + } + + // Call this method to get a file from the server + void requestRemoteFile(String text, String localPath) { + try { + String temp = text; + receivePath = localPath; + + writeRfbFileTransferMsg(rfbFileTransferRequest, 0, 1, // 0 : compression not supported - 1 : compression supported + temp.length(), temp); + } catch (IOException e) { + System.err.println(e); + } + } + + // Internally used when transferring file from server. Here, the server sends + // a rfb packet signalling that it is ready to send the file requested + void receiveFileHeader() throws IOException { + fFileReceptionRunning = true; + fFileReceptionError = false; + int size = is.readInt(); + int length = is.readInt(); + + String tempName = ""; + for (int i = 0; i < length; i++) { + tempName += (char) is.readUnsignedByte(); + } + + // sf@2004 - Read the high part of file size (not yet in rfbFileTransferMsg for + // backward compatibility reasons...) + int sizeH = is.readInt(); + long lSize = ((long) (sizeH) << 32) + size; + + receiveFileSize = lSize; + fileSize = 0; + fileChunkCounter = 0; + String fileName = receivePath; + fos = new FileOutputStream(fileName); + writeRfbFileTransferMsg(rfbFileHeader, 0, 0, 0, null); + } + + // Internally used when transferring file from server. This method receives one chunk + // of the file + void receiveFileChunk() throws IOException { + // sf@2004 - Size = 0 means file chunck not compressed + int size = is.readInt(); + boolean fCompressed = (size != 0); + int length = is.readInt(); + fileChunkCounter++; + + // sf@2004 - allocates buffers for file chunck reception and decompression + byte[] ReceptionBuffer = new byte[length + 32]; + + // Read the incoming file data + // Todo: check error ! + is.readFully(ReceptionBuffer, 0, length); + + if (fCompressed) { + int bufSize = sz_rfbBlockSize + 1024; // Todo: set a more accurate value here + int decompressedSize = 0; + byte[] DecompressionBuffer = new byte[bufSize]; + Inflater myInflater = new Inflater(); + myInflater.setInput(ReceptionBuffer); + try { + decompressedSize = myInflater.inflate(DecompressionBuffer); + } catch (DataFormatException e) { + System.err.println(e); + } + // Todo: check error ! + fos.write(DecompressionBuffer, 0, decompressedSize); + fileSize += decompressedSize; + } else { + // Todo: check error ! + fos.write(ReceptionBuffer, 0, length); + fileSize += length; + } + + /* + * for (int i = 0; i < length; i++) { fos.write(is.readUnsignedByte()); fileSize++; } + */ + + if (fAbort == true) { + fAbort = false; + fFileReceptionError = true; + writeRfbFileTransferMsg(rfbAbortFileTransfer, 0, 0, 0, null); + + } + // sf@2004 - For old FT protocole only + /* + * if(fileChunkCounter==10) { writeRfbFileTransferMsg(rfbFileHeader,0,0,0,null); fileChunkCounter=0; } + */ + } + + // Internally used when transferring file from server. Server signals end of file. + void endOfReceiveFile(boolean fReceptionOk) throws IOException { + int size = is.readInt(); + int length = is.readInt(); + fileSize = 0; + fos.close(); + + if (fReceptionOk && !fFileReceptionError) { + } else { + // sf@2004 - Delete the incomplete receieved file for now (until we use Delta Transfer) + File f = new File(receivePath); + f.delete(); + } + + fFileReceptionError = false; + fFileReceptionRunning = false; + } + + // Call this method to read the contents of the server directory + void readServerDirectory(String text) { + try { + String temp = text; + writeRfbFileTransferMsg(rfbDirContentRequest, rfbRDirContent, 0, temp.length(), temp); + } catch (IOException e) { + System.err.println(e); + } + + } + + // Internally used to receive list of drives available on the server + void readFTPMsgDriveList() throws IOException { + String str = ""; + for (int i = 0; i < 4; i++) { + is.readUnsignedByte(); + } + int length = is.readInt(); + for (int i = 0; i < length; i++) { + char temp = (char) is.readUnsignedByte(); + if (temp != '\0') { + str += temp; + } + } + + } + + // Internally used to receive directory content from server + // Here, the server marks the start of the directory listing + void readFTPMsgDirectoryList() throws IOException { + is.readInt(); + int length = is.readInt(); + if (length == 0) { + inDirectory2 = false; + } else { + // sf@2004 - New FT protocole sends remote directory name + String str = ""; + for (int i = 0; i < length; i++) { + char temp = (char) is.readUnsignedByte(); + if (temp != '\0') { + str += temp; + } + } + // viewer.ftp.changeRemoteDirectory(str); + + } + } + + // Internally used to receive directory content from server + // Here, the server sends one file/directory with it's attributes + void readFTPMsgDirectoryListContent() throws IOException { + String fileName = "", alternateFileName = ""; + byte contentType = 0; + int contentParamT = 0; + int contentParam = 0; + byte temp = 0; + int dwFileAttributes, nFileSizeHigh, nFileSizeLow, dwReserved0, dwReserved1; + long ftCreationTime, ftLastAccessTime, ftLastWriteTime; + char cFileName, cAlternateFileName; + int length = 0; + is.readInt(); + length = is.readInt(); + dwFileAttributes = is.readInt(); + length -= 4; + ftCreationTime = is.readLong(); + length -= 8; + ftLastAccessTime = is.readLong(); + length -= 8; + ftLastWriteTime = is.readLong(); + length -= 8; + nFileSizeHigh = is.readInt(); + length -= 4; + nFileSizeLow = is.readInt(); + length -= 4; + dwReserved0 = is.readInt(); + length -= 4; + dwReserved1 = is.readInt(); + length -= 4; + cFileName = (char) is.readUnsignedByte(); + length--; + while (cFileName != '\0') { + fileName += cFileName; + cFileName = (char) is.readUnsignedByte(); + length--; + } + cAlternateFileName = (char) is.readByte(); + length--; + while (length != 0) { + alternateFileName += cAlternateFileName; + cAlternateFileName = (char) is.readUnsignedByte(); + length--; + } + if (dwFileAttributes == 268435456 || dwFileAttributes == 369098752 || dwFileAttributes == 285212672 || dwFileAttributes == 271056896 || dwFileAttributes == 824705024 + || dwFileAttributes == 807927808 || dwFileAttributes == 371720192 || dwFileAttributes == 369623040) { + fileName = " [" + fileName + "]"; + remoteDirsList.add(fileName); // sf@2004 + } else { + remoteFilesList.add(" " + fileName); // sf@2004 + } + + // a.add(fileName); + } + + // Internally used to read directory content of server. + // Here, server signals end of directory. + void readFTPMsgDirectoryListEndContent() throws IOException { + is.readInt(); + int length = is.readInt(); + + // sf@2004 + a.clear(); + for (int i = 0; i < remoteDirsList.size(); i++) + a.add(remoteDirsList.get(i)); + for (int i = 0; i < remoteFilesList.size(); i++) + a.add(remoteFilesList.get(i)); + remoteDirsList.clear(); + remoteFilesList.clear(); + } + + // sf@2004 - Read the destination file checksums data + // We don't use it for now + void ReceiveDestinationFileChecksums() throws IOException { + int size = is.readInt(); + int length = is.readInt(); + + byte[] ReceptionBuffer = new byte[length + 32]; + + // Read the incoming file data + is.readFully(ReceptionBuffer, 0, length); + + /* + * String csData = ""; for (int i = 0; i < length; i++) { csData += (char) is.readUnsignedByte(); } + */ + + // viewer.ftp.connectionStatus.setText("Received: 0 bytes of " + size + " bytes"); + } + + // + // 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, boolean fGreyScale) // sf@2005 + 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; + b[17] = (byte) (fGreyScale ? 1 : 0); // sf@2005 + + 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 { + // if (!viewer.ftp.isVisible()) { + + 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; + + // + // 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); + } + +// added by noctis + + public boolean tryAuthenticate(String us, String pw) throws Exception { + + readVersionMsg(); + + System.out.println("RFB server supports protocol version " + serverMajor + "." + serverMinor); + + writeVersionMsg(); + + int authScheme = readAuthScheme(); + + switch (authScheme) { + + case RfbProto.NoAuth: + System.out.println("No authentication needed"); + return true; + + case RfbProto.VncAuth: + byte[] challenge = new byte[16]; + is.readFully(challenge); + + if (pw.length() > 8) + pw = pw.substring(0, 8); // Truncate to 8 chars + + // vncEncryptBytes in the UNIX libvncauth truncates password + // after the first zero byte. We do to. + 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); + + int authResult = is.readInt(); + + switch (authResult) { + case RfbProto.VncAuthOK: + System.out.println("VNC authentication succeeded"); + return true; + case RfbProto.VncAuthFailed: + System.out.println("VNC authentication failed"); + break; + case RfbProto.VncAuthTooMany: + throw new Exception("VNC authentication failed - too many tries"); + default: + throw new Exception("Unknown VNC authentication result " + authResult); + } + break; + + case RfbProto.MsLogon: + System.out.println("MS-Logon (DH) detected"); + break; + default: + throw new Exception("Unknown VNC authentication scheme " + authScheme); + } + return false; + } + + void doProtocolInitialisation() throws IOException { + + writeClientInit(); + + readServerInit(); + + System.out.println("Desktop name is " + desktopName); + System.out.println("Desktop size is " + framebufferWidth + " x " + framebufferHeight); + } + + +} diff --git a/main/app_share/NoctisVncCanvas.class b/main/app_share/NoctisVncCanvas.class new file mode 100644 index 0000000000..43840eb7c1 Binary files /dev/null and b/main/app_share/NoctisVncCanvas.class differ diff --git a/main/app_share/NoctisVncCanvas.java b/main/app_share/NoctisVncCanvas.java new file mode 100644 index 0000000000..af2c9176e6 --- /dev/null +++ b/main/app_share/NoctisVncCanvas.java @@ -0,0 +1,1679 @@ +// +// Copyright (C) 2001,2002 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.Canvas; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.awt.image.MemoryImageSource; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.zip.Inflater; + + +// +// VncCanvas is a subclass of Canvas which draws a VNC desktop on it. +// + +class NoctisVncCanvas + extends Canvas + implements KeyListener, MouseListener, MouseMotionListener { + + VncViewer viewer; + RfbProto rfb; + ColorModel cm8_256c, cm8_64c, cm8_8c, cm24; + Color[] colors; + int bytesPixel; + + Image memImage; + Graphics memGraphics; + + Image rawPixelsImage; + MemoryImageSource pixelsSource; + byte[] pixels8; + int[] pixels24; + + // 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 constructor. + // + + NoctisVncCanvas(VncViewer v) throws IOException { + viewer = v; + rfb = viewer.rfb; + + tightInflaters = new Inflater[4]; + + // sf@2005 - Adding more color modes + cm8_256c = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); + cm8_64c = new DirectColorModel(8, (3 << 4), (3 << 2), (3 << 0)); + cm8_8c = new DirectColorModel(8, (1 << 2), (1 << 1), (1 << 0)); + + cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); + + colors = new Color[256]; + // sf@2005 - Now Default + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_256c.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); + } + + // + // Callback methods to determine geometry of our Component. + // + + public Dimension getPreferredSize() { + return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight); + } + + public Dimension getMinimumSize() { + return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight); + } + + public Dimension getMaximumSize() { + return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight); + } + + // + // All painting is performed here. + // + + public void update(Graphics g) { + paint(g); + } + + public void paint(Graphics g) { + synchronized (memImage) { + g.drawImage(memImage, 0, 0, null); + } + 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); + } + } + } + + // + // 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); + } + } else if (!enable && inputEnabled) { + inputEnabled = false; + removeMouseListener(this); + removeMouseMotionListener(this); + if (viewer.showControls) { + viewer.buttonPanel.enableRemoteAccessControls(false); + } + } + } + + + public void setPixelFormat() throws IOException { + // sf@2005 - Adding more color modes + if (viewer.options.eightBitColors > 0) + { + viewer.options.oldEightBitColors = viewer.options.eightBitColors; + switch (viewer.options.eightBitColors) + { + case 1: // 256 + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_256c.getRGB(i)); + rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6, false); + break; + case 2: // 64 + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_64c.getRGB(i)); + rfb.writeSetPixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0, false); + break; + case 3: // 8 + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_8c.getRGB(i)); + rfb.writeSetPixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0, false); + break; + case 4: // 4 (Grey) + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_64c.getRGB(i)); + rfb.writeSetPixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0, true); + break; + case 5: // 2 (B&W) + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_8c.getRGB(i)); + rfb.writeSetPixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0, true); + break; + } + bytesPixel = 1; + } + else + { + rfb.writeSetPixelFormat( + 32, + 24, + false, + true, + 255, + 255, + 255, + 16, + 8, + 0, + false); + bytesPixel = 4; + } + updateFramebufferSize(); + } + + void updateFramebufferSize() { + + // Useful shortcuts. + int fbWidth = rfb.framebufferWidth; + int fbHeight = rfb.framebufferHeight; + + // 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.createImage(fbWidth, fbHeight); + memGraphics = memImage.getGraphics(); + } else if ( + memImage.getWidth(null) != fbWidth + || memImage.getHeight(null) != fbHeight) { + synchronized (memImage) { + memImage = viewer.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]; + + // sf@2005 + ColorModel cml = cm8_8c; + + // sf@2005 + switch (viewer.options.eightBitColors) + { + case 1: + cml = cm8_256c; + break; + + case 2: + case 4: + cml = cm8_64c; + break; + case 3: + case 5: + cml = cm8_8c; + break; + } + + pixelsSource = + new MemoryImageSource( + fbWidth, + fbHeight, + cml, + pixels8, + 0, + fbWidth); + } else { + pixels8 = null; + pixels24 = new int[fbWidth * fbHeight]; + + pixelsSource = + new MemoryImageSource( + fbWidth, + fbHeight, + cm24, + pixels24, + 0, + fbWidth); + } + pixelsSource.setAnimated(true); + rawPixelsImage = createImage(pixelsSource); + + // Update the size of desktop containers. + if (viewer.inSeparateFrame) { + if (viewer.desktopScrollPane != null) + resizeDesktopFrame(); + } else { + setSize(fbWidth, fbHeight); + } + } + + void resizeDesktopFrame() { + setSize(rfb.framebufferWidth, rfb.framebufferHeight); + + // FIXME: Find a better way to determine correct size of a + // ScrollPane. -- const + Insets insets = viewer.desktopScrollPane.getInsets(); + viewer.desktopScrollPane.setSize( + rfb.framebufferWidth + 2 * Math.min(insets.left, insets.right), + rfb.framebufferHeight + 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; + 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); + + // + // main dispatch loop + // + + while (true) { + // Read message type from the server. + int msgType = rfb.readServerMessageType(); + + // Process the message depending on its type. + switch (msgType) { + case RfbProto.FramebufferUpdate : + rfb.readFramebufferUpdate(); + + for (int i = 0; i < rfb.updateNRects; i++) { + rfb.readFramebufferUpdateRectHdr(); + 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; + } + + switch (rfb.updateRectEncoding) { + case RfbProto.EncodingRaw : + handleRawRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingCopyRect : + 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 : + handleHextileRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingZlib : + handleZlibRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingTight : + handleTightRect(rx, ry, rw, rh); + break; + // marscha - PointerPos + case RfbProto.EncodingPointerPos : + handleCursorPosUpdate(rx, ry); + break; + default : + throw new Exception( + "Unknown RFB rectangle encoding " + + rfb.updateRectEncoding); + } + } + + 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. + if (viewer.deferUpdateRequests > 0) { + synchronized (rfb) { + try { + rfb.wait(viewer.deferUpdateRequests); + } catch (InterruptedException e) { + } + } + } + + // Before requesting framebuffer update, check if the pixel + // format should be changed. If it should, request full update + // instead of an incremental one. + if ((viewer.options.eightBitColors > 0) && (bytesPixel != 1) + || + (viewer.options.eightBitColors == 0) && (bytesPixel == 1) + || + (viewer.options.eightBitColors != viewer.options.oldEightBitColors) + ) + { + setPixelFormat(); + fullUpdateNeeded = true; + } + + rfb.writeFramebufferUpdateRequest( + 0, + 0, + rfb.framebufferWidth, + rfb.framebufferHeight, + !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; + + case RfbProto.rfbFileTransfer : + viewer.rfb.readRfbFileTransferMsg(); + 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.is.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.is.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.is.readInt(); + + byte[] bg_buf = new byte[bytesPixel]; + rfb.is.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.is.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.is.readInt(); + + byte[] bg_buf = new byte[bytesPixel]; + rfb.is.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.is.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.is.readUnsignedByte(); + 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.is.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.is.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.is.readUnsignedByte(); + int bufsize = nSubrects * 2; + if ((subencoding & rfb.HextileSubrectsColoured) != 0) { + bufsize += nSubrects * bytesPixel; + } + byte[] buf = new byte[bufsize]; + rfb.is.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 Zlib-encoded rectangle. + // + + void handleZlibRect(int x, int y, int w, int h) throws Exception { + + int nBytes = rfb.is.readInt(); + + if (zlibBuf == null || zlibBufLen < nBytes) { + zlibBufLen = nBytes * 2; + zlibBuf = new byte[zlibBufLen]; + } + + rfb.is.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.is.readUnsignedByte(); + 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.is.readUnsignedByte(); + memGraphics.setColor(colors[idx]); + if (rfb.rec != null) { + rfb.rec.writeByte(idx); + } + } else { + byte[] buf = new byte[3]; + rfb.is.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) { + + // Read JPEG data. + byte[] jpegData = new byte[rfb.readCompactLen()]; + rfb.is.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.is.readUnsignedByte(); + if (rfb.rec != null) { + rfb.rec.writeByte(filter_id); + } + if (filter_id == rfb.TightFilterPalette) { + numColors = rfb.is.readUnsignedByte() + 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.is.readFully(palette8); + if (rfb.rec != null) { + rfb.rec.write(palette8); + } + } else { + byte[] buf = new byte[numColors * 3]; + rfb.is.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.is.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.is.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.is.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.is.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.is.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. + repaint(viewer.deferScreenUpdates, x, y, w, h); + } + + // + // 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()); + } + 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) { + } + + ////////////////////////////////////////////////////////////////// + // + // Handle cursor shape updates (XCursor and RichCursor encodings). + // + + boolean showSoftCursor = false; + + int[] softCursorPixels; + MemoryImageSource softCursorSource; + Image softCursor; + + int cursorX = 0, cursorY = 0; + int cursorWidth, cursorHeight; + int hotX, hotY; + + // + // Handle cursor shape update (XCursor and RichCursor encodings). + // + + synchronized void handleCursorShapeUpdate( + int encodingType, + int xhot, + int yhot, + int width, + int height) + throws IOException { + + int bytesPerRow = (width + 7) / 8; + int bytesMaskData = bytesPerRow * height; + + softCursorFree(); + + if (width * height == 0) + return; + + // Ignore cursor shape data if requested by user. + + if (viewer.options.ignoreCursorUpdates) { + if (encodingType == rfb.EncodingXCursor) { + rfb.is.skipBytes(6 + bytesMaskData * 2); + } else { + // rfb.EncodingRichCursor + rfb.is.skipBytes(width * height + bytesMaskData); + } + return; + } + + // Decode cursor pixel data. + + softCursorPixels = new int[width * height]; + + if (encodingType == rfb.EncodingXCursor) { + + // Read foreground and background colors of the cursor. + byte[] rgb = new byte[6]; + rfb.is.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.is.readFully(pixBuf); + byte[] maskBuf = new byte[bytesMaskData]; + rfb.is.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.is.readFully(pixBuf); + byte[] maskBuf = new byte[bytesMaskData]; + rfb.is.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 = 0; + // sf@2005 + switch (viewer.options.eightBitColors) + { + case 1: + result = cm8_256c.getRGB(pixBuf[i]); + break; + case 2: + case 4: + result = cm8_64c.getRGB(pixBuf[i]); + break; + case 3: + case 5: + result = cm8_8c.getRGB(pixBuf[i]); + break; + } + } + else + { + result = + 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF) + << 16 | (pixBuf[i * 4 + 2] & 0xFF) + << 8 | (pixBuf[i * 4 + 3] & 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 = 0; +// sf@2005 + switch (viewer.options.eightBitColors) + { + case 1: + result = cm8_256c.getRGB(pixBuf[i]); + break; + case 2: + case 4: + result = cm8_64c.getRGB(pixBuf[i]); + break; + case 3: + case 5: + result = cm8_8c.getRGB(pixBuf[i]); + break; + } } + else + { + result = + 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF) + << 16 | (pixBuf[i * 4 + 2] & 0xFF) + << 8 | (pixBuf[i * 4 + 3] & 0xFF); + } + } else { + result = 0; // Transparent pixel + } + softCursorPixels[i++] = result; + } + } + + } + + // Draw the cursor on an off-screen image. + + softCursorSource = + new MemoryImageSource(width, height, softCursorPixels, 0, width); + softCursor = createImage(softCursorSource); + + // Set remaining data associated with cursor. + + cursorWidth = width; + cursorHeight = height; + hotX = xhot; + hotY = yhot; + + showSoftCursor = true; + + // Show the cursor. + + repaint( + viewer.deferCursorUpdates, + cursorX - hotX, + cursorY - hotY, + cursorWidth, + cursorHeight); + } + + // + // marscha - PointerPos + // Handle cursor position update (PointerPos encoding). + // + + synchronized void handleCursorPosUpdate( + int x, + int y) { + if (x >= rfb.framebufferWidth) + x = rfb.framebufferWidth - 1; + if (y >= rfb.framebufferHeight) + y = rfb.framebufferHeight - 1; + + softCursorMove(x, y); + } + + // + // softCursorMove(). Moves soft cursor into a particular location. + // + + synchronized void softCursorMove(int x, int y) { + if (showSoftCursor) { + repaint( + viewer.deferCursorUpdates, + cursorX - hotX, + cursorY - hotY, + cursorWidth, + cursorHeight); + repaint( + viewer.deferCursorUpdates, + x - hotX, + y - hotY, + cursorWidth, + cursorHeight); + } + + cursorX = x; + cursorY = y; + } + + // + // softCursorFree(). Remove soft cursor, dispose resources. + // + + synchronized void softCursorFree() { + if (showSoftCursor) { + showSoftCursor = false; + softCursor = null; + softCursorSource = null; + softCursorPixels = null; + + repaint( + viewer.deferCursorUpdates, + cursorX - hotX, + cursorY - hotY, + cursorWidth, + cursorHeight); + } + } +} diff --git a/main/app_share/OptionsFrame.class b/main/app_share/OptionsFrame.class new file mode 100644 index 0000000000..52a816936a Binary files /dev/null and b/main/app_share/OptionsFrame.class differ diff --git a/main/app_share/OptionsFrame.java b/main/app_share/OptionsFrame.java new file mode 100644 index 0000000000..f0750d34d3 --- /dev/null +++ b/main/app_share/OptionsFrame.java @@ -0,0 +1,398 @@ +// +// 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", + "Share desktop", + }; + + static String[][] values = { + { "Raw", "RRE", "CoRRE", "Hextile", "Zlib", "Tight" }, + { "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" }, + { "Full", "256", "64", "8", "4 (Grey)", "2 (B&W)" }, + { "Normal", "Reversed" }, + { "Yes", "No" }, + { "Yes", "No" }, + }; + + final int + encodingIndex = 0, + compressLevelIndex = 1, + jpegQualityIndex = 2, + cursorUpdatesIndex = 3, + useCopyRectIndex = 4, + eightBitColorsIndex = 5, + mouseButtonIndex = 6, + viewOnlyIndex = 7, + shareDesktopIndex = 8; + + 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[] encodings = new int[20]; + int nEncodings; + + int compressLevel; + int jpegQuality; + + int eightBitColors; // sf@2005 + int oldEightBitColors; + + boolean requestCursorUpdates; + boolean ignoreCursorUpdates; + + boolean reverseMouseButtons2And3; + boolean shareDesktop; + boolean viewOnly; + + // + // Constructor. Set up the labels and choices from the names and values + // arrays. + // + + OptionsFrame(VncViewer v) { + super("Ultr@VNC 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("Tight"); + choices[compressLevelIndex].select("Default"); + choices[jpegQualityIndex].select("6"); + choices[cursorUpdatesIndex].select("Enable"); + choices[useCopyRectIndex].select("Yes"); + choices[eightBitColorsIndex].select("256"); + choices[mouseButtonIndex].select("Normal"); + choices[viewOnlyIndex].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); + } + } + } + } + + // 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 + // the encodings array appropriately. It also calls the VncViewer's + // setEncodings method to send a message to the RFB server if + // necessary. + // + + void setEncodings() { + nEncodings = 0; + if (choices[useCopyRectIndex].getSelectedItem().equals("Yes")) { + encodings[nEncodings++] = RfbProto.EncodingCopyRect; + } + + int 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("Zlib")) { + preferredEncoding = RfbProto.EncodingZlib; + enableCompressLevel = true; + } else if (choices[encodingIndex].getSelectedItem().equals("Tight")) { + preferredEncoding = RfbProto.EncodingTight; + enableCompressLevel = true; + } + + encodings[nEncodings++] = preferredEncoding; + if (preferredEncoding != RfbProto.EncodingHextile) { + encodings[nEncodings++] = RfbProto.EncodingHextile; + } + if (preferredEncoding != RfbProto.EncodingTight) { + encodings[nEncodings++] = RfbProto.EncodingTight; + } + if (preferredEncoding != RfbProto.EncodingZlib) { + encodings[nEncodings++] = RfbProto.EncodingZlib; + } + if (preferredEncoding != RfbProto.EncodingCoRRE) { + encodings[nEncodings++] = RfbProto.EncodingCoRRE; + } + if (preferredEncoding != RfbProto.EncodingRRE) { + encodings[nEncodings++] = RfbProto.EncodingRRE; + } + + // Handle compression level setting. + + if (enableCompressLevel) { + labels[compressLevelIndex].setEnabled(true); + choices[compressLevelIndex].setEnabled(true); + try { + compressLevel = + Integer.parseInt(choices[compressLevelIndex].getSelectedItem()); + } + catch (NumberFormatException e) { + compressLevel = -1; + } + if (compressLevel >= 1 && compressLevel <= 9) { + encodings[nEncodings++] = + RfbProto.EncodingCompressLevel0 + compressLevel; + } else { + compressLevel = -1; + } + } else { + labels[compressLevelIndex].setEnabled(false); + choices[compressLevelIndex].setEnabled(false); + } + + // Handle JPEG quality setting. + + if (preferredEncoding == RfbProto.EncodingTight && (eightBitColors == 0)) { + labels[jpegQualityIndex].setEnabled(true); + choices[jpegQualityIndex].setEnabled(true); + try { + jpegQuality = + Integer.parseInt(choices[jpegQualityIndex].getSelectedItem()); + } + catch (NumberFormatException e) { + jpegQuality = -1; + } + if (jpegQuality >= 0 && jpegQuality <= 9) { + encodings[nEncodings++] = + RfbProto.EncodingQualityLevel0 + jpegQuality; + } else { + jpegQuality = -1; + } + } else { + labels[jpegQualityIndex].setEnabled(false); + choices[jpegQualityIndex].setEnabled(false); + } + + // Request cursor shape updates if necessary. + + requestCursorUpdates = + !choices[cursorUpdatesIndex].getSelectedItem().equals("Disable"); + + if (requestCursorUpdates) { + encodings[nEncodings++] = RfbProto.EncodingXCursor; + encodings[nEncodings++] = RfbProto.EncodingRichCursor; + ignoreCursorUpdates = + choices[cursorUpdatesIndex].getSelectedItem().equals("Ignore"); + // marscha - PointerPos + if (!ignoreCursorUpdates) { + encodings[nEncodings++] = RfbProto.EncodingPointerPos; + } + } + + encodings[nEncodings++] = RfbProto.EncodingLastRect; + encodings[nEncodings++] = RfbProto.EncodingNewFBSize; + + 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() { + + // sf@2005 - Adding more color modes + if (choices[eightBitColorsIndex].getSelectedItem().equals("Full")) + eightBitColors = 0; + else if (choices[eightBitColorsIndex].getSelectedItem().equals("256")) + eightBitColors = 1; + else if (choices[eightBitColorsIndex].getSelectedItem().equals("64")) + eightBitColors = 2; + else if (choices[eightBitColorsIndex].getSelectedItem().equals("8")) + eightBitColors = 3; + else if (choices[eightBitColorsIndex].getSelectedItem().equals("4 (Grey)")) + eightBitColors = 4; + else if (choices[eightBitColorsIndex].getSelectedItem().equals("2 (B&W)")) + eightBitColors = 5; + + boolean enableJPEG = (eightBitColors == 0) && + choices[encodingIndex].getSelectedItem().equals("Tight"); + + labels[jpegQualityIndex].setEnabled(enableJPEG); + choices[jpegQualityIndex].setEnabled(enableJPEG); + } + + // + // setOtherOptions looks at the "other" choices (ones which don't set the + // encoding or the color format) 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"); + } + + + // + // 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(); + + } else if (source == choices[eightBitColorsIndex]) { + + setColorFormat(); + + } else if (source == choices[mouseButtonIndex] || + source == choices[shareDesktopIndex] || + source == choices[viewOnlyIndex]) { + + 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 --git a/main/app_share/POCConnection.class b/main/app_share/POCConnection.class new file mode 100644 index 0000000000..80baa8e6c4 Binary files /dev/null and b/main/app_share/POCConnection.class differ diff --git a/main/app_share/POCConnection.java b/main/app_share/POCConnection.java new file mode 100644 index 0000000000..cbe46e9a6f --- /dev/null +++ b/main/app_share/POCConnection.java @@ -0,0 +1,40 @@ +import java.io.InputStream; + + +public class POCConnection { + + public static final String HOST = "127.0.0.1"; + public static final int PORT = 5900; + + /** + * @param args + */ + public static void main(String[] args) { + + try { + NoctisRfbProto rfb = new NoctisRfbProto(HOST, PORT, new ConfigClientBean()); + System.out.println("TRACE: [POCConnection]-[main] - authentification : "+rfb.tryAuthenticate("", "1234")); //TODO: remove trace + + rfb.doProtocolInitialisation(); + + InputStream in = rfb.is; + System.out.println("reading..."); + int read = in.read(); + int c = 0; + if (read<0) { + System.out.println("nothing found on stream."); + } + while ((read >= 0)&&(c<30)) { + System.out.print(read); + read = in.read(); + c++; + } + in.close(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + +} diff --git a/main/app_share/README b/main/app_share/README new file mode 100644 index 0000000000..d11b042bbd --- /dev/null +++ b/main/app_share/README @@ -0,0 +1,444 @@ + +Ultr@VNC 1.0.0 RC19 Java Viewer +=================================== + +Copyright (C) 2002-2005 Ultr@VNC Team. All Rights Reserved. +Copyright (C) 2004 Kenn Min Chong, John Witchel. All Rights Reserved. +Copyright (C) 2004 Alban Chazot. All Rights Reserved. +Copyright (C) 2001,2002 HorizonLive.com, Inc. All Rights Reserved. +Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved. +Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + +This software is distributed under the GNU General Public Licence as +published by the Free Software Foundation. See the file LICENCE.TXT for the +conditions under which this software is made available. VNC also contains +code from other sources. See the Acknowledgements section below, and the +individual files for details of the conditions under which they are made +available. + +**************************************************************************** + + + +This JavaViewer supports Ultr@VNC FileTransfer ( >= RC19 protocole version) +as well as Ultr@VNC MS Logon. + +So even from a Unix or Mac machine you can use a simple Web browser and do +some FileTransfer with the UltraVNC Win32 Server, as well as taking advantage +of the more secure MSLogon authentication method. + +When loaded in the WebBrowser (browsing http://YourUltraServerIP:5800), the +user is prompted to accept or reject the Ultr@VNC JavaViewer applet signed +certificate. It is necessary as this new JavaViewer does some FileTransfer +and consequently needs to access the user's local drives. + +WARNING: This JavaViewer Applet can't be loaded using the default MS IE JVM +as it doesn't support Swing. You must install a Java JVM (v1.3 or >). + +What still needs to be implemented as soon as possible (that is already in +the Win32 Viewer): + + - Add files datetime and size in the files lists + - Add files multi-selection for transfer + - Add a "Parent Directory" button the the files panels. + - Directory transfer + - Delta transfer + - ... + + +NEW: added support for more color modes, usefull on slow connections: +64 Colors, 8 Colors, 2 B/W, 8 and 4 Grey Scale colors. + + +Compiling and Running the JavaViewer +==================================== + +** To compile the JavaViewer + +1. Edit the "mk.bat" file and replace the path "c:\soft" with the +path where your Java sdk stands. + +2. Save and execute your mk.bat + +3. If you want to sign the generated applet so it can be embedded into +Ultra winvnc.exe and injected into web browsers on connections to port 5800, +use the keytool.exe and jarsigner.exe programs that can be found in the +Java sdk. + +4. To replace the winvnc JavaViewer applet with your modifed +and signed applet, copy the generated .class files and the vncviewer.jar +file into winvnc\res directory and recompile WinVNC. + + + +** To run this JavaViewer as a Java application, you must have the Java +Runtime installed. + +1. Edit the "run.bat" file and replace the path "c:\Ultravnc\JavaViewer" +with the path where you've copied the JavaViewer, then replace "127.0.0.1" +with the IP adress or network name of the machine where WinVNC server is +running. + +2. Save and Execute run.bat + +=> If the path and adress you've written are correct, you should be prompted +for the VNC password. If MS Logon is required on the server, you will also be +prompted for Windows Username. + + +Under Linux and Mac, you can also make a batchfile that executes this command: + +java.exe -cp **YourJavaViewerFullPath** VncViewer HOST **YourServerIP** PORT 5900 + + +As soon as this new JavaViewer reaches the "beta" stage we'll put the source +code in Ultr@VNC CVS repository. For now, the source code is available on demand +only. If you know Java, have time and want to improve the Ultr@VNC project, your +help would be greatly appreciated. + + + + + + + +Compiling from the sources +========================== + +To compile all the .java files to .class files, simply do: + + % make all + +This will also generate a JAR (Java archive) file containing all the classes. +Copy all the .class files, the .jar file and the .vnc files to an +installation directory (e.g. /usr/local/vnc/classes): + + % cp *.class *.jar *.vnc /usr/local/vnc/classes + +Make sure that the vncserver script is configured to point to the +installation directory. + + +Configuration +============= + +Ultr@VNC Java viewer supports a number of parameters allowing you to +customize its behaviour. Most parameter names copy settings available from +the Options frame in the Java viewer. Both parameter names and their values +are case-insensitive, with one exception for the "PASSWORD" parameter. Here +is the full list of parameters supported in Ultr@VNC Java viewer: + +--> "HOST" (no GUI equivalent) + + Value: host name or IP address of the VNC server. + Default: in applet mode, the host from which the applet was loaded. + + This parameter tells the viewer which server to connect to. Normally, + it's not needed, because default Java security policy allow connections + from applets to the only one host anyway, and that is the host from which + the applet was loaded. + +--> "PORT" (no GUI equivalent) + + Value: TCP port number on the VNC server. + Default: none. + + This parameter is required in all cases. Note that this port is not the + one used for HTTP connection from the browser, it is the port used for + RFB connection. Usually, VNC servers use ports 58xx for HTTP connections, + and ports 59xx for RFB connections. Thus, most likely, this parameter + should be set to something like 5900, 5901 etc. + +--> "PASSWORD" + + Value: session password in plan text. + Default: none, ask user. + + DO NOT EVER USE THIS PARAMETER, unless you really know what you are + doing. It's extremely dangerous from the security point of view. When + this parameter is set, the viewer won't ever ask for a password. + +--> "ENCPASSWORD" + + Value: encrypted session password in hex-ascii. + Default: none, ask user. + + The same as the "PASSWORD" parameter but DES-encrypted using a fixed key. + Its value should be represented in hex-ascii e.g. "494015f9a35e8b22". + This parameter has higher priority over the "PASSWORD" parameter. DO NOT + EVER USE THIS PARAMETER, unless you really know what you are doing. It's + extremely dangerous from the security point of view, and encryption does + not actually help here since the decryption key is always known. + +--> "Encoding" + + Values: "Raw", "RRE", "CoRRE", "Hextile", "Zlib", "Tight". + Default: "Tight". + + The preferred encoding. "Hextile" is a good choice for fast networks, + while "Tight" is better suited for low-bandwidth connections. From the + other side, the "Tight" decoder in Ultr@VNC Java viewer seems to be more + efficient than "Hextile" decoder so it's possible that this default + setting can be ok for fast networks too. + +--> "Compression level" + + Values: "Default", "1", "2", "3", "4", "5", "6", "7", "8", "9". + Default: "Default". ;-) + + Use specified compression level for "Tight" and "Zlib" encodings. Level 1 + uses minimum of CPU time on the server but achieves weak compression + ratios. Level 9 offers best compression but may be slow in terms of CPU + time consumption on the server side. Use high levels with very slow + network connections, and low levels when working over higher-speed + networks. The "Default" value means that the server's default compression + level should be used. + +--> "JPEG image quality" + + Values: "JPEG off", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9". + Default: "6". + + Use the specified image quality level in "Tight" encoding. Quality level + 0 denotes bad image quality but very impressive compression ratios, while + level 9 offers very good image quality at lower compression ratios. If + the value is "JPEG off", the server will not use lossy JPEG compression + in "Tight" encoding. + +--> "Cursor shape updates" + + Values: "Enable", "Ignore", "Disable". + Default: "Enable". + + Cursor shape updates is a protocol extension used to handle remote cursor + movements locally on the client side, saving bandwidth and eliminating + delays in mouse pointer movement. Note that current implementation of + cursor shape updates does not allow a client to track mouse cursor + position at the server side. This means that clients would not see mouse + cursor movements if mouse was moved either locally on the server, or by + another remote VNC client. Set this parameter to "Disable" if you always + want to see real cursor position on the remote side. Setting this option + to "Ignore" is similar to "Enable" but the remote cursor will not be + visible at all. This can be a reasonable setting if you don't care about + cursor shape and don't want to see two mouse cursors, one above another. + +--> "Use CopyRect" + + Values: "Yes", "No". + Default: "Yes". + + The "CopyRect" encoding saves bandwidth and drawing time when parts of + the remote screen are moving around. Most likely, you don't want to + change this setting. + +--> "Restricted colors" + + Values: "Yes", "No". + Default: "No". + + If set to "No", then 24-bit color format is used to represent pixel data. + If set to "Yes", then only 8 bits are used to represent each pixel. 8-bit + color format can save bandwidth, but colors may look very inaccurate. + +--> "Mouse buttons 2 and 3" + + Values: "Normal", "Reversed". + Default: "Normal". + + If set to "Reversed", then right mouse button (button 2) will act as it + was middle mouse button (button 3), and vice versa. + +--> "View only" + + Values: "Yes", "No". + Default: "No". + + If set to "Yes", then all keyboard and mouse events in the desktop window + will be silently ignored and will not be passed to the remote side. + +--> "Share desktop" + + Values: "Yes", "No". + Default: "Yes". + + Share the connection with other clients on the same VNC server. The exact + behaviour in each case depends on the server configuration. + +--> "Open new window" (no GUI equivalent, applicable only in the applet mode) + + Values: "Yes", "No". + Default: "No". + + Operate in a separate window. This makes possible resizing the desktop, + and adds scroll bars when necessary. If the server supports variable + desktop size, the window will resize automatically when remote desktop + size changes. + +--> "Show controls" (no GUI equivalent) + + Values: "Yes", "No". + Default: "Yes". + + Set to "No" if you want to get rid of that button panel at the top. + +--> "Show offline desktop" (no GUI equivalent) + + Values: "Yes", "No". + Default: "No". + + If set to "Yes", the viewer would continue to display desktop even + if the remote side has closed the connection. In this case, if the + button panel is enabled, then the "Disconnect" button would be + changed to "Hide desktop" after the connection is lost. + +--> "Defer screen updates" (no GUI equivalent) + + Value: time in milliseconds. + Default: "20". + + When updating the desktop contents after receiving an update from server, + schedule repaint within the specified number of milliseconds. Small delay + helps to coalesce several small updates into one drawing operation, + improving CPU usage. Set this parameter to 0 to disable deferred updates. + +--> "Defer cursor updates" (no GUI equivalent) + + Value: time in milliseconds. + Default: "10". + + When updating the desktop after moving the mouse, schedule repaint within + the specified number of milliseconds. This setting makes sense only when + "Cursor shape updates" parameter is set to "Enable". Small delay helps to + coalesce several small updates into one drawing operation, improving CPU + usage. Set this parameter to 0 to disable deferred cursor updates. + +--> "Defer update requests" (no GUI equivalent) + + Value: time in milliseconds. + Default: "50". + + After processing an update received from server, wait for the specified + number of milliseconds before requesting next screen update. Such delay + will end immediately on every mouse or keyboard event if not in the "view + only" mode. Small delay helps the server to coalesce several small + updates into one framebuffer update, improving both bandwidth and CPU + usage. Increasing the parameter value does not affect responsiveness on + mouse and keyboard events, but causes delays in updating the screen when + there is no mouse and keyboard activity on the client side. + + +RECORDING VNC SESSIONS +====================== + +Current version of the Ultr@VNC Java viewer is able to record VNC (RFB) +sessions in files for later playback. The data format in saved session files +is compatible with the rfbproxy program written by Tim Waugh. Most important +thing about session recording is that it's supported only if Java security +manager allows access to local filesystem. Typically, it would not work for +unsigned applets. To use this feature, either use Ultr@VNC Java viewer as a +standalone application (Java Runtime Environment or Java Development Kit +should be installed), or as a signed applet. The code checks if it's possible +to support session recording, and if everything's fine, the new "Record" +button should appear in the button panel. Pressing this button opens new +window which controls session recording. The GUI is pretty self-explained. + +Other important facts about session recording: + +--> All sessions are recorded in the 24-bit color format. If you use + restricted colors (8-bit format), it will be temporarly switched to + 24-bit mode during session recording. + +--> All sessions are recorded with cursor shape updates turned off. This is + necessary to represent remote cursor movements in recorded sessions. + +--> Closing and re-opening the recording control window does not affect the + recording. It's not necessary to keep that window open during recording a + session. + +--> Avoid using Zlib encoding when recording sessions. It's ok if you started + recording BEFORE the connection to the VNC server has been established, + but if you started recording during an active session, all Zlib sessions + will be saved Raw-encoded (that is, without compression at all). Zlib + decoding depends on the pixel data received earlier, thus saving the data + received from the server at an arbitrary moment is not sufficient to + decompress it correctly. And there is no way to say Zlib decoder to reset + decompressor's state -- that's a limitation of the Zlib encoder. The + viewer could re-compress raw pixel data again before saving Zlib-encoded + sessions, but unfortunately Java API does not allow to flush zlib data + streams making it impossible to save Zlib-encoded RFB pixel data without + using native code. + +--> Usually, Tight encoding is the most suitable one for session recording, + but some of the issues described above for the Zlib encoding affect the + Tight encoding as well. Unlike Zlib sessions, Tight-encoded sessions are + always saved Tight-encoded, but the viewer has to re-compress parts of + data to synchronize encoder's and decoder's zlib streams. And, due to + Java zlib API limitations, zlib streams' states have to be reset on each + compressed rectangle, causing compression ratios to be lower than in the + original VNC session. If you want to achieve the best possible + performance, turn recording on BEFORE connecting to the VNC server, + otherwise CPU usage and compression ratios may be notably less efficient. + + +HINTS +===== + +--> To refresh remote desktop in the view-only mode, press "r" or "R" + on the keyboard. + + +ACKNOWLEDGEMENTS +================ + +This distribution contains Java DES software by Dave Zimmerman + and Jef Poskanzer . This 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. + + 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/ diff --git a/main/app_share/RecordingFrame.class b/main/app_share/RecordingFrame.class new file mode 100644 index 0000000000..a80a73af95 Binary files /dev/null and b/main/app_share/RecordingFrame.class differ diff --git a/main/app_share/RecordingFrame.java b/main/app_share/RecordingFrame.java new file mode 100644 index 0000000000..41b822c51a --- /dev/null +++ b/main/app_share/RecordingFrame.java @@ -0,0 +1,322 @@ +// +// 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.awt.Button; +import java.awt.Color; +import java.awt.FileDialog; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.Panel; +import java.awt.TextField; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.File; + +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"); + } 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 --git a/main/app_share/RfbProto.class b/main/app_share/RfbProto.class new file mode 100644 index 0000000000..6168ace16a Binary files /dev/null and b/main/app_share/RfbProto.class differ diff --git a/main/app_share/RfbProto.java b/main/app_share/RfbProto.java new file mode 100644 index 0000000000..d42ca5029e --- /dev/null +++ b/main/app_share/RfbProto.java @@ -0,0 +1,1878 @@ +// Copyright (C) 2002-2004 Ultr@VNC Team. All Rights Reserved. +// Copyright (C) 2004 Kenn Min Chong, John Witchel. All Rights Reserved. +// Copyright (C) 2001,2002 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. +// + +// +// RfbProto.java +//4/19/04 + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.util.ArrayList; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + + +class RfbProto { + + final String versionMsg = "RFB 003.003\n"; + final static int ConnFailed = 0, NoAuth = 1, VncAuth = 2, MsLogon = 0xfffffffa; + final static int VncAuthOK = 0, VncAuthFailed = 1, VncAuthTooMany = 2; + + final static int FramebufferUpdate = 0, + SetColourMapEntries = 1, + Bell = 2, + ServerCutText = 3, + rfbFileTransfer = 7; + + final int SetPixelFormat = 0, + FixColourMapEntries = 1, + SetEncodings = 2, + FramebufferUpdateRequest = 3, + KeyboardEvent = 4, + PointerEvent = 5, + ClientCutText = 6; + + final static int EncodingRaw = 0, + EncodingCopyRect = 1, + EncodingRRE = 2, + EncodingCoRRE = 4, + EncodingHextile = 5, + EncodingZlib = 6, + EncodingTight = 7, + EncodingCompressLevel0 = 0xFFFFFF00, + EncodingQualityLevel0 = 0xFFFFFFE0, + EncodingXCursor = 0xFFFFFF10, + EncodingRichCursor = 0xFFFFFF11, + EncodingPointerPos = 0xFFFFFF18, // marscha - PointerPos + EncodingLastRect = 0xFFFFFF20, + EncodingNewFBSize = 0xFFFFFF21; + + final int HextileRaw = (1 << 0); + final int HextileBackgroundSpecified = (1 << 1); + final int HextileForegroundSpecified = (1 << 2); + final int HextileAnySubrects = (1 << 3); + final int HextileSubrectsColoured = (1 << 4); + + final static int TightExplicitFilter = 0x04; + final static int TightFill = 0x08; + final static int TightJpeg = 0x09; + final static int TightMaxSubencoding = 0x09; + final static int TightFilterCopy = 0x00; + final static int TightFilterPalette = 0x01; + final static int TightFilterGradient = 0x02; + + final static int TightMinToCompress = 12; + + // sf@2004 - FileTransfer part + ArrayList remoteDirsList; + ArrayList remoteFilesList; + ArrayList a; + boolean fFTInit = true; // sf@2004 + boolean fFTAllowed = true; + boolean fAbort = false; + boolean fFileReceptionError = false; + boolean fFileReceptionRunning = false; + boolean inDirectory2; + FileOutputStream fos; + FileInputStream fis; + String sendFileSource; + String receivePath; + long fileSize; + long receiveFileSize; + long fileChunkCounter; + + final static int sz_rfbFileTransferMsg = 12, + // FileTransfer Content types and Params defines + rfbDirContentRequest = 1, + // Client asks for the content of a given Server directory + rfbDirPacket = 2, // Full directory name or full file name. + // Null content means end of Directory + rfbFileTransferRequest = 3, + // Client asks the server for the tranfer of a given file + rfbFileHeader = 4, + // First packet of a file transfer, containing file's features + rfbFilePacket = 5, // One slice of the file + rfbEndOfFile = 6, + // End of file transfer (the file has been received or error) + rfbAbortFileTransfer = 7, + // The file transfer must be aborted, whatever the state + rfbFileTransferOffer = 8, + // The client offers to send a file to the server + rfbFileAcceptHeader = 9, // The server accepts or rejects the file + rfbCommand = 10, + // The Client sends a simple command (File Delete, Dir create etc...) + rfbCommandReturn = 11, + // New FT Protocole (V2) The zipped checksums of the destination file (Delta Transfer) + rfbFileChecksums = 12, + // The Client receives the server's answer about a simple command + // rfbDirContentRequest client Request - content params + rfbRDirContent = 1, // Request a Server Directory contents + rfbRDrivesList = 2, // Request the server's drives list + + // rfbDirPacket & rfbCommandReturn server Answer - content params + rfbADirectory = 1, // Reception of a directory name + rfbAFile = 2, // Reception of a file name + rfbADrivesList = 3, // Reception of a list of drives + rfbADirCreate = 4, // Response to a create dir command + rfbADirDelete = 5, // Response to a delete dir command + rfbAFileCreate = 6, // Response to a create file command + rfbAFileDelete = 7, // Response to a delete file command + + // rfbCommand Command - content params + rfbCDirCreate = 1, // Request the server to create the given directory + rfbCDirDelete = 2, // Request the server to delete the given directory + rfbCFileCreate = 3, // Request the server to create the given file + rfbCFileDelete = 4, // Request the server to delete the given file + + // Errors - content params or "size" field + rfbRErrorUnknownCmd = 1, // Unknown FileTransfer command. + rfbRErrorCmd = 0xFFFFFFFF, + + // Error when a command fails on remote side (ret in "size" field) + sz_rfbBlockSize = 8192, // new FT protocole (v2) + + // Size of a File Transfer packet (before compression) + sz_rfbZipDirectoryPrefix = 9; + + String rfbZipDirectoryPrefix = "!UVNCDIR-\0"; + // Transfered directory are zipped in a file with this prefix. Must end with "-" + + // End of FileTransfer part + + String host; + int port; + Socket sock; + DataInputStream is; + OutputStream os; + OutputStreamWriter osw; + + SessionRecorder rec; + boolean inNormalProtocol = false; + VncViewer viewer; + + // 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- 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- 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; + + /* added for noctis relay + * + */ + private void sendRelayCommand(OutputStream out, String serverID) throws IOException { + out.write(IProtocolCommand.CLIENT_CONNECTION); + byte[] serverIDData = serverID.getBytes(); + out.write(serverIDData.length); + out.write(serverIDData); + out.flush(); + } + + // + // Constructor. Make TCP connection to RFB server. + // + + RfbProto(String h, int p, VncViewer v, String serverID) throws IOException { + viewer = v; + host = h; + port = p; + sock = new Socket(host, port); + is = + new DataInputStream( + new BufferedInputStream(sock.getInputStream(), 16384)); + os = sock.getOutputStream(); + osw = new OutputStreamWriter(sock.getOutputStream()); + + // send a command to relay for create a new connection to the specific server. + sendRelayCommand(os, serverID); + + inDirectory2 = false; + a = new ArrayList(); + // sf@2004 + remoteDirsList = new ArrayList(); + remoteFilesList = new ArrayList(); + + sendFileSource = ""; + } + + void close() { + try { + sock.close(); + if (rec != null) { + rec.close(); + rec = null; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // + // Read server's protocol version message + // + + int serverMajor, serverMinor; + + void readVersionMsg() throws Exception { + + byte[] b = new byte[12]; + + is.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'); + } + + // + // Write our protocol version message + // + + void writeVersionMsg() throws IOException { + os.write(versionMsg.getBytes()); + } + + // + // Find out the authentication scheme. + // + + int readAuthScheme() throws Exception { + int authScheme = is.readInt(); + + switch (authScheme) { + + case ConnFailed : + int reasonLen = is.readInt(); + byte[] reason = new byte[reasonLen]; + is.readFully(reason); + throw new Exception(new String(reason)); + + case NoAuth : + case VncAuth : + case MsLogon: + return authScheme; + + default : + throw new Exception( + "Unknown authentication scheme from RFB server: " + + authScheme); + + } + } + + // + // 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 = is.readUnsignedShort(); + framebufferHeight = is.readUnsignedShort(); + bitsPerPixel = is.readUnsignedByte(); + depth = is.readUnsignedByte(); + bigEndian = (is.readUnsignedByte() != 0); + trueColour = (is.readUnsignedByte() != 0); + redMax = is.readUnsignedShort(); + greenMax = is.readUnsignedShort(); + blueMax = is.readUnsignedShort(); + redShift = is.readUnsignedByte(); + greenShift = is.readUnsignedByte(); + blueShift = is.readUnsignedByte(); + byte[] pad = new byte[3]; + is.readFully(pad); + int nameLength = is.readInt(); + byte[] name = new byte[nameLength]; + is.readFully(name); + desktopName = new String(name); + + 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.getBytes()); + rec.writeIntBE(NoAuth); + 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; + + 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 = is.readUnsignedByte(); + + // 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 { + is.readByte(); + updateNRects = is.readUnsignedShort(); + + // 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 = is.readUnsignedShort(); + updateRectY = is.readUnsignedShort(); + updateRectW = is.readUnsignedShort(); + updateRectH = is.readUnsignedShort(); + updateRectEncoding = is.readInt(); + + if (updateRectEncoding == EncodingZlib + || 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 == EncodingLastRect + || updateRectEncoding == EncodingNewFBSize) + 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 = is.readUnsignedShort(); + copyRectSrcY = is.readUnsignedShort(); + + // If the session is being recorded: + if (rec != null) { + rec.writeShortBE(copyRectSrcX); + rec.writeShortBE(copyRectSrcY); + } + } + + // + // Read a ServerCutText message + // + + String readServerCutText() throws IOException { + byte[] pad = new byte[3]; + is.readFully(pad); + int len = is.readInt(); + byte[] text = new byte[len]; + is.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] = is.readUnsignedByte(); + int byteCount = 1; + int len = portion[0] & 0x7F; + if ((portion[0] & 0x80) != 0) { + portion[1] = is.readUnsignedByte(); + byteCount++; + len |= (portion[1] & 0x7F) << 7; + if ((portion[1] & 0x80) != 0) { + portion[2] = is.readUnsignedByte(); + byteCount++; + len |= (portion[2] & 0xFF) << 14; + } + } + + if (rec != null && recordFromBeginning) + for (int i = 0; i < byteCount; i++) + rec.writeByte(portion[i]); + + return len; + } + + //Author: Kenn Min Chong///////////////////////////////////////////// + + //Read/Write a rfbFileTransferMsg + /*typedef struct _rfbFileTransferMsg { + CARD8 type; // always rfbFileTransfer + CARD8 contentType; // See defines below + CARD16 contentParam;// Other possible content classification (Dir or File name, etc..) + CARD32 size; // FileSize or packet index or error or other + CARD32 length; + // followed by data char text[length] + } rfbFileTransferMsg; + */ + + // Parsing Rfb message to see what type + + void readRfbFileTransferMsg() throws IOException + { + int contentType = is.readUnsignedByte(); + int contentParamT = is.readUnsignedByte(); + int contentParam = contentParamT; + contentParamT = is.readUnsignedByte(); + contentParamT = contentParamT << 8; + contentParam = contentParam | contentParamT; + if (contentType == rfbRDrivesList || contentType == rfbDirPacket) + { + readDriveOrDirectory(contentParam); + } + else if (contentType == rfbFileHeader) + { + receiveFileHeader(); + } + else if (contentType == rfbFilePacket) + { + receiveFileChunk(); + } + else if (contentType == rfbEndOfFile) + { + endOfReceiveFile(true); // Ok + } + else if (contentType == rfbAbortFileTransfer) + { + if (fFileReceptionRunning) + { + endOfReceiveFile(false); // Error + } + else + { + // sf@2004 - Todo: Add TestPermission + // System.out.println("File Transfer Aborted!"); + } + + } + else if (contentType == rfbCommandReturn) + { + createDirectoryorDeleteFile(contentParam); + } + else if (contentType == rfbFileAcceptHeader) + { + sendFile(); + } + else if (contentType == rfbFileChecksums) + { + ReceiveDestinationFileChecksums(); + } + else + { + System.out.println("ContentType: " + contentType); + } + } + + //Refactored from readRfbFileTransferMsg() + public void createDirectoryorDeleteFile(int contentParam) + throws IOException { + if (contentParam == rfbADirCreate) + { + createRemoteDirectoryFeedback(); + } + else if (contentParam == rfbAFileDelete) + { + deleteRemoteFileFeedback(); + } + } + + //Refactored from readRfbFileTransferMsg() + public void readDriveOrDirectory(int contentParam) throws IOException { + if (contentParam == rfbADrivesList) + { + readFTPMsgDriveList(); + } + else if (contentParam == rfbADirectory && !inDirectory2) + { + inDirectory2 = true; + readFTPMsgDirectoryList(); + } + else if (contentParam == rfbADirectory && inDirectory2) + { + readFTPMsgDirectoryListContent(); + } + else if (contentParam == 0) + { + readFTPMsgDirectoryListEndContent(); + inDirectory2 = false; + } + else + { + System.out.println("ContentParam: " + contentParam); + } + } + + // Internally used. Write an Rfb message to the server + void writeRfbFileTransferMsg( + int contentType, + int contentParam, + long size, // 0 : compression not supported - 1 : compression supported + long length, + String text) throws IOException + { + byte b[] = new byte[12]; + + b[0] = (byte) rfbFileTransfer; + b[1] = (byte) contentType; + b[2] = (byte) contentParam; + + byte by = 0; + long c = 0; + length++; + c = size & 0xFF000000; + by = (byte) (c >>> 24); + b[4] = by; + c = size & 0xFF0000; + by = (byte) (c >>> 16); + b[5] = by; + c = size & 0xFF00; + by = (byte) (c >>> 8); + b[6] = by; + c = size & 0xFF; + by = (byte) c; + b[7] = by; + + c = length & 0xFF000000; + by = (byte) (c >>> 24); + b[8] = by; + c = length & 0xFF0000; + by = (byte) (c >>> 16); + b[9] = by; + c = length & 0xFF00; + by = (byte) (c >>> 8); + b[10] = by; + c = length & 0xFF; + by = (byte) c; + b[11] = by; + os.write(b); + + + if (text != null) + { + byte byteArray[] = text.getBytes(); + byte byteArray2[] = new byte[byteArray.length + 1]; + for (int i = 0; i < byteArray.length; i++) { + byteArray2[i] = byteArray[i]; + } + byteArray2[byteArray2.length - 1] = 0; + os.write(byteArray2); + } + + } + + //Internally used. Write an rfb message to the server for sending files ONLY + int writeRfbFileTransferMsgForSendFile( + int contentType, + int contentParam, + long size, + long length, + String source + ) throws IOException + { + File f = new File(source); + fis = new FileInputStream(f); + byte byteBuffer[] = new byte[sz_rfbBlockSize]; + int bytesRead = fis.read(byteBuffer); + long counter=0; + boolean fError = false; + + // sf@ - Manage compression + boolean fCompress = true; + Deflater myDeflater = new Deflater(); + byte[] CompressionBuffer = new byte[sz_rfbBlockSize + 1024]; + int compressedSize = 0; + + while (bytesRead!=-1) + { + counter += bytesRead; + myDeflater.setInput(byteBuffer, 0, bytesRead); + myDeflater.finish(); + compressedSize = myDeflater.deflate(CompressionBuffer); + myDeflater.reset(); + // If the compressed data is larger than the original one, we're dealing with + // already compressed data + if (compressedSize > bytesRead) + fCompress = false; + this.writeRfbFileTransferMsg( + contentType, + contentParam, + (fCompress ? 1 : 0), + (fCompress ? compressedSize-1 : bytesRead-1), + null + ); + // Todo: Test write error ! + os.write( + fCompress ? CompressionBuffer : byteBuffer, + 0, + fCompress ? compressedSize : bytesRead + ); + + // Todo: test read error ! + bytesRead = fis.read(byteBuffer); + + // viewer.ftp.connectionStatus.setText("Sent: "+ counter + " bytes of "+ f.length() + " bytes"); + viewer.ftp.jProgressBar.setValue((int)((counter * 100) / f.length())); + viewer.ftp.connectionStatus.setText(">>> Sending File: " + source + " - Size: " + f.length() + " bytes - Progress: " + ((counter * 100) / f.length()) + "%"); + + if (fAbort == true) + { + fAbort = false; + fError = true; + break; + } + try + { + Thread.sleep(5); + } + catch(InterruptedException e) + { + System.err.println("Interrupted"); + } + } + + writeRfbFileTransferMsg(fError ? rfbAbortFileTransfer : rfbEndOfFile, 0, 0, 0, null); + fis.close(); + return (fError ? -1 : 1); + } + + //This method is internally used to send the file to the server once the server is ready + void sendFile() + { + try + { + viewer.ftp.disableButtons(); + int size = is.readInt(); + int length = is.readInt(); + for (int i = 0; i < length; i++) + { + System.out.print((char) is.readUnsignedByte()); + } + + int ret = writeRfbFileTransferMsgForSendFile( + rfbFilePacket, + 0, + 0, + 0, + sendFileSource); + + viewer.ftp.refreshRemoteLocation(); + if (ret != 1) + { + viewer.ftp.connectionStatus.setText(" > Error - File NOT sent"); + viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - File: <" + sendFileSource) + "> was not correctly sent (aborted by user or error)",0); + } + else + { + viewer.ftp.connectionStatus.setText(" > File sent"); + viewer.ftp.historyComboBox.insertItemAt(new String(" > File: <" + sendFileSource) + "> was sent to Remote Machine",0); + } + viewer.ftp.historyComboBox.setSelectedIndex(0); + viewer.ftp.enableButtons(); + } + catch (IOException e) + { + System.err.println(e); + } + } + + //Call this method to send a file from local pc to server + void offerLocalFile(String source, String destinationPath) + { + try + { + sendFileSource = source; + File f = new File(source); + // sf@2004 - Add support for huge files + long lSize = f.length(); + int iLowSize = (int)(lSize & 0x00000000FFFFFFFF); + int iHighSize = (int)(lSize >> 32); + + String temp = destinationPath + f.getName(); + writeRfbFileTransferMsg( + rfbFileTransferOffer, + 0, + iLowSize, // f.length(), + temp.length(), + temp); + + // sf@2004 - Send the high part of the size + byte b[] = new byte[4]; + byte by = 0; + long c = 0; + c = iHighSize & 0xFF000000; + by = (byte) (c >>> 24); + b[0] = by; + c = iHighSize & 0xFF0000; + by = (byte) (c >>> 16); + b[1] = by; + c = iHighSize & 0xFF00; + by = (byte) (c >>> 8); + b[2] = by; + c = iHighSize & 0xFF; + by = (byte) c; + b[3] = by; + os.write(b); + } + catch (IOException e) + { + System.err.println(e); + } + } + + //Internally used. + //Handles acknowledgement that the file has been deleted on the server + void deleteRemoteFileFeedback() throws IOException + { + is.readInt(); + int length = is.readInt(); + String f = ""; + for (int i = 0; i < length; i++) + { + f += (char)is.readUnsignedByte(); + } + + viewer.ftp.refreshRemoteLocation(); + viewer.ftp.historyComboBox.insertItemAt(new String(" > Deleted File On Remote Machine: " + f.substring(0, f.length()-1)),0); + viewer.ftp.historyComboBox.setSelectedIndex(0); + } + + //Call this method to delete a file at server + void deleteRemoteFile(String text) + { + try + { + String temp = text; + writeRfbFileTransferMsg( + rfbCommand, + rfbCFileDelete, + 0, + temp.length(), + temp); + } + catch (IOException e) + { + System.err.println(e); + } + } + + //Internally used. + // Handles acknowledgement that the directory has been created on the server + void createRemoteDirectoryFeedback() throws IOException + { + is.readInt(); + int length = is.readInt(); + String f=""; + for (int i = 0; i < length; i++) + { + f += (char)is.readUnsignedByte(); + } + viewer.ftp.refreshRemoteLocation(); + viewer.ftp.historyComboBox.insertItemAt(new String(" > Created Directory on Remote Machine: " + f.substring(0, f.length()-1)),0); + viewer.ftp.historyComboBox.setSelectedIndex(0); + } + + //Call this method to create a directory at server + void createRemoteDirectory(String text) + { + try + { + String temp = text; + writeRfbFileTransferMsg( + rfbCommand, + rfbCDirCreate, + 0, + temp.length(), + temp); + } + catch (IOException e) + { + System.err.println(e); + } + } + + //Call this method to get a file from the server + void requestRemoteFile(String text, String localPath) + { + try + { + String temp = text; + receivePath = localPath; + + writeRfbFileTransferMsg( + rfbFileTransferRequest, + 0, + 1, // 0 : compression not supported - 1 : compression supported + temp.length(), + temp); + } + catch (IOException e) + { + System.err.println(e); + } + } + + //Internally used when transferring file from server. Here, the server sends + //a rfb packet signalling that it is ready to send the file requested + void receiveFileHeader() throws IOException + { + fFileReceptionRunning = true; + fFileReceptionError = false; + viewer.ftp.disableButtons(); + int size = is.readInt(); + int length = is.readInt(); + + String tempName = ""; + for (int i = 0; i < length; i++) + { + tempName += (char) is.readUnsignedByte(); + } + + // sf@2004 - Read the high part of file size (not yet in rfbFileTransferMsg for + // backward compatibility reasons...) + int sizeH = is.readInt(); + long lSize = ((long)(sizeH) << 32) + size; + + receiveFileSize = lSize; + viewer.ftp.connectionStatus.setText("Received: 0 bytes of " + lSize + " bytes"); + fileSize=0; + fileChunkCounter = 0; + String fileName = receivePath; + fos = new FileOutputStream(fileName); + writeRfbFileTransferMsg(rfbFileHeader, 0, 0, 0, null); + } + + //Internally used when transferring file from server. This method receives one chunk + //of the file + void receiveFileChunk() throws IOException + { + // sf@2004 - Size = 0 means file chunck not compressed + int size = is.readInt(); + boolean fCompressed = (size != 0); + int length = is.readInt(); + fileChunkCounter++; + + // sf@2004 - allocates buffers for file chunck reception and decompression + byte[] ReceptionBuffer = new byte[length + 32]; + + // Read the incoming file data + // Todo: check error ! + is.readFully(ReceptionBuffer,0, length); + + if (fCompressed) + { + int bufSize = sz_rfbBlockSize + 1024; // Todo: set a more accurate value here + int decompressedSize = 0; + byte[] DecompressionBuffer = new byte[bufSize]; + Inflater myInflater = new Inflater(); + myInflater.setInput(ReceptionBuffer); + try + { + decompressedSize = myInflater.inflate(DecompressionBuffer); + } + catch (DataFormatException e) + { + System.err.println(e); + } + // Todo: check error ! + fos.write(DecompressionBuffer, 0, decompressedSize); + fileSize += decompressedSize; + } + else + { + // Todo: check error ! + fos.write(ReceptionBuffer, 0, length); + fileSize += length; + } + + /* + for (int i = 0; i < length; i++) + { + fos.write(is.readUnsignedByte()); + fileSize++; + } + */ + + // viewer.ftp.connectionStatus.setText("Received: " + fileSize + " bytes of "+ receiveFileSize+ " bytes" ); + viewer.ftp.jProgressBar.setValue((int)((fileSize * 100) / receiveFileSize)); + viewer.ftp.connectionStatus.setText(">>> Receiving File: " + receivePath + " - Size: " + receiveFileSize + " bytes - Progress: " + ((fileSize * 100) / receiveFileSize) + "%"); + + if (fAbort == true) + { + fAbort = false; + fFileReceptionError = true; + writeRfbFileTransferMsg(rfbAbortFileTransfer, 0, 0, 0, null); + + } + // sf@2004 - For old FT protocole only + /* + if(fileChunkCounter==10) + { + writeRfbFileTransferMsg(rfbFileHeader,0,0,0,null); + fileChunkCounter=0; + } + */ + } + + //Internally used when transferring file from server. Server signals end of file. + void endOfReceiveFile(boolean fReceptionOk) throws IOException + { + int size = is.readInt(); + int length = is.readInt(); + fileSize=0; + fos.close(); + + viewer.ftp.refreshLocalLocation(); + if (fReceptionOk && !fFileReceptionError) + { + viewer.ftp.connectionStatus.setText(" > File successfully received"); + viewer.ftp.historyComboBox.insertItemAt(new String(" > File: <" + receivePath + "> received from Remote Machine" ),0); + } + else + { + // sf@2004 - Delete the incomplete receieved file for now (until we use Delta Transfer) + File f = new File(receivePath); + f.delete(); + viewer.ftp.connectionStatus.setText(" > Error - File NOT received"); + viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - File: <" + receivePath + "> not correctly received from Remote Machine (aborted by user or error)") ,0); + } + + fFileReceptionError = false; + fFileReceptionRunning = false; + viewer.ftp.historyComboBox.setSelectedIndex(0); + viewer.ftp.enableButtons(); + } + + //Call this method to read the contents of the server directory + void readServerDirectory(String text) + { + try + { + String temp = text; + writeRfbFileTransferMsg( + rfbDirContentRequest, + rfbRDirContent, + 0, + temp.length(), + temp); + } + catch (IOException e) + { + System.err.println(e); + } + + } + + //Internally used to receive list of drives available on the server + void readFTPMsgDriveList() throws IOException + { + String str = ""; + for (int i = 0; i < 4; i++) + { + is.readUnsignedByte(); + } + int length = is.readInt(); + for (int i = 0; i < length; i++) + { + char temp = (char) is.readUnsignedByte(); + if (temp != '\0') + { + str += temp; + } + } + viewer.ftp.printDrives(str); + + // sf@2004 + // Finds the first readable drive and populates the local directory + viewer.ftp.changeLocalDirectory(viewer.ftp.getFirstReadableLocalDrive()); + // Populate the remote directory + viewer.ftp.changeRemoteDrive(); + viewer.ftp.refreshRemoteLocation(); + + } + + //Internally used to receive directory content from server + //Here, the server marks the start of the directory listing + void readFTPMsgDirectoryList() throws IOException + { + is.readInt(); + int length = is.readInt(); + if (length == 0) + { + readFTPMsgDirectorydriveNotReady(); + inDirectory2 = false; + } + else + { + // sf@2004 - New FT protocole sends remote directory name + String str = ""; + for (int i = 0; i < length; i++) + { + char temp = (char) is.readUnsignedByte(); + if (temp != '\0') + { + str += temp; + } + } + // viewer.ftp.changeRemoteDirectory(str); + + } + } + + //Internally used to receive directory content from server + //Here, the server sends one file/directory with it's attributes + void readFTPMsgDirectoryListContent() throws IOException + { + String fileName = "", alternateFileName = ""; + byte contentType = 0; + int contentParamT = 0; + int contentParam = 0; + byte temp = 0; + int dwFileAttributes, + nFileSizeHigh, + nFileSizeLow, + dwReserved0, + dwReserved1; + long ftCreationTime, ftLastAccessTime, ftLastWriteTime; + char cFileName, cAlternateFileName; + int length = 0; + is.readInt(); + length = is.readInt(); + dwFileAttributes = is.readInt(); + length -= 4; + ftCreationTime = is.readLong(); + length -= 8; + ftLastAccessTime = is.readLong(); + length -= 8; + ftLastWriteTime = is.readLong(); + length -= 8; + nFileSizeHigh = is.readInt(); + length -= 4; + nFileSizeLow = is.readInt(); + length -= 4; + dwReserved0 = is.readInt(); + length -= 4; + dwReserved1 = is.readInt(); + length -= 4; + cFileName = (char) is.readUnsignedByte(); + length--; + while (cFileName != '\0') + { + fileName += cFileName; + cFileName = (char) is.readUnsignedByte(); + length--; + } + cAlternateFileName = (char) is.readByte(); + length--; + while (length != 0) + { + alternateFileName += cAlternateFileName; + cAlternateFileName = (char) is.readUnsignedByte(); + length--; + } + if (dwFileAttributes == 268435456 + || dwFileAttributes == 369098752 + || dwFileAttributes == 285212672 + || dwFileAttributes == 271056896 + || dwFileAttributes == 824705024 + || dwFileAttributes == 807927808 + || dwFileAttributes == 371720192 + || dwFileAttributes == 369623040) + { + fileName = " [" + fileName + "]"; + remoteDirsList.add(fileName); // sf@2004 + } + else + { + remoteFilesList.add(" " + fileName); // sf@2004 + } + + // a.add(fileName); + } + + //Internally used to read directory content of server. + //Here, server signals end of directory. + void readFTPMsgDirectoryListEndContent() throws IOException + { + is.readInt(); + int length = is.readInt(); + + // sf@2004 + a.clear(); + for (int i = 0; i < remoteDirsList.size(); i++) + a.add(remoteDirsList.get(i)); + for (int i = 0; i < remoteFilesList.size(); i++) + a.add(remoteFilesList.get(i)); + remoteDirsList.clear(); + remoteFilesList.clear(); + + viewer.ftp.printDirectory(a); + } + + //Internally used to signify the drive requested is not ready + + void readFTPMsgDirectorydriveNotReady() throws IOException + { + System.out.println("Remote Drive unavailable"); + viewer.ftp.connectionStatus.setText(" > WARNING - Remote Drive unavailable (possibly restricted access or media not present)"); + viewer.ftp.remoteStatus.setText("WARNING: Remote Drive unavailable"); + } + + //Call this method to request the list of drives on the server. + void readServerDriveList() + { + try + { + viewer.rfb.writeRfbFileTransferMsg( + RfbProto.rfbDirContentRequest, + RfbProto.rfbRDrivesList, + 0, + 0, + null); + } + catch (IOException e) + { + System.err.println(e); + } + } + + // sf@2004 - Read the destination file checksums data + // We don't use it for now + void ReceiveDestinationFileChecksums() throws IOException + { + int size = is.readInt(); + int length = is.readInt(); + + byte[] ReceptionBuffer = new byte[length + 32]; + + // Read the incoming file data + is.readFully(ReceptionBuffer,0, length); + + /* + String csData = ""; + for (int i = 0; i < length; i++) + { + csData += (char) is.readUnsignedByte(); + } + */ + + // viewer.ftp.connectionStatus.setText("Received: 0 bytes of " + size + " bytes"); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// + // + // Write a FramebufferUpdateRequest message + // + + void writeFramebufferUpdateRequest( + int x, + int y, + int w, + int h, + boolean incremental) + throws IOException { + if (!viewer.ftp.isVisible()) { + 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, + boolean fGreyScale) // sf@2005 + 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; + b[17] = (byte) (fGreyScale ? 1 : 0); // sf@2005 + + 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 { + // if (!viewer.ftp.isVisible()) { + + 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 { + if (!viewer.ftp.isVisible()) { + 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 { + if (!viewer.ftp.isVisible()) { + 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 + if ((key < 0xff00) || (key > 0xffff)) + 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); + } +} diff --git a/main/app_share/SessionRecorder.class b/main/app_share/SessionRecorder.class new file mode 100644 index 0000000000..f90242d716 Binary files /dev/null and b/main/app_share/SessionRecorder.class differ diff --git a/main/app_share/SessionRecorder.java b/main/app_share/SessionRecorder.java new file mode 100644 index 0000000000..a02ce98895 --- /dev/null +++ b/main/app_share/SessionRecorder.java @@ -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 --git a/main/app_share/VncCanvas.class b/main/app_share/VncCanvas.class new file mode 100644 index 0000000000..708de5b13e Binary files /dev/null and b/main/app_share/VncCanvas.class differ diff --git a/main/app_share/VncCanvas.java b/main/app_share/VncCanvas.java new file mode 100644 index 0000000000..f93059bada --- /dev/null +++ b/main/app_share/VncCanvas.java @@ -0,0 +1,1679 @@ +// +// Copyright (C) 2001,2002 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.Canvas; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.awt.image.MemoryImageSource; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.zip.Inflater; + + +// +// VncCanvas is a subclass of Canvas which draws a VNC desktop on it. +// + +class VncCanvas + extends Canvas + implements KeyListener, MouseListener, MouseMotionListener { + + VncViewer viewer; + private RfbProto rfb; + ColorModel cm8_256c, cm8_64c, cm8_8c, cm24; + Color[] colors; + int bytesPixel; + + Image memImage; + Graphics memGraphics; + + Image rawPixelsImage; + MemoryImageSource pixelsSource; + byte[] pixels8; + int[] pixels24; + + // 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 constructor. + // + + VncCanvas(VncViewer v) throws IOException { + viewer = v; + rfb = viewer.rfb; + + tightInflaters = new Inflater[4]; + + // sf@2005 - Adding more color modes + cm8_256c = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); + cm8_64c = new DirectColorModel(8, (3 << 4), (3 << 2), (3 << 0)); + cm8_8c = new DirectColorModel(8, (1 << 2), (1 << 1), (1 << 0)); + + cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); + + colors = new Color[256]; + // sf@2005 - Now Default + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_256c.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); + } + + // + // Callback methods to determine geometry of our Component. + // + + public Dimension getPreferredSize() { + return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight); + } + + public Dimension getMinimumSize() { + return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight); + } + + public Dimension getMaximumSize() { + return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight); + } + + // + // All painting is performed here. + // + + public void update(Graphics g) { + paint(g); + } + + public void paint(Graphics g) { + synchronized (memImage) { + g.drawImage(memImage, 0, 0, null); + } + 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); + } + } + } + + // + // 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); + } + } else if (!enable && inputEnabled) { + inputEnabled = false; + removeMouseListener(this); + removeMouseMotionListener(this); + if (viewer.showControls) { + viewer.buttonPanel.enableRemoteAccessControls(false); + } + } + } + + + public void setPixelFormat() throws IOException { + // sf@2005 - Adding more color modes + if (viewer.options.eightBitColors > 0) + { + viewer.options.oldEightBitColors = viewer.options.eightBitColors; + switch (viewer.options.eightBitColors) + { + case 1: // 256 + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_256c.getRGB(i)); + rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6, false); + break; + case 2: // 64 + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_64c.getRGB(i)); + rfb.writeSetPixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0, false); + break; + case 3: // 8 + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_8c.getRGB(i)); + rfb.writeSetPixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0, false); + break; + case 4: // 4 (Grey) + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_64c.getRGB(i)); + rfb.writeSetPixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0, true); + break; + case 5: // 2 (B&W) + for (int i = 0; i < 256; i++) + colors[i] = new Color(cm8_8c.getRGB(i)); + rfb.writeSetPixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0, true); + break; + } + bytesPixel = 1; + } + else + { + rfb.writeSetPixelFormat( + 32, + 24, + false, + true, + 255, + 255, + 255, + 16, + 8, + 0, + false); + bytesPixel = 4; + } + updateFramebufferSize(); + } + + void updateFramebufferSize() { + + // Useful shortcuts. + int fbWidth = rfb.framebufferWidth; + int fbHeight = rfb.framebufferHeight; + + // 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.createImage(fbWidth, fbHeight); + memGraphics = memImage.getGraphics(); + } else if ( + memImage.getWidth(null) != fbWidth + || memImage.getHeight(null) != fbHeight) { + synchronized (memImage) { + memImage = viewer.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]; + + // sf@2005 + ColorModel cml = cm8_8c; + + // sf@2005 + switch (viewer.options.eightBitColors) + { + case 1: + cml = cm8_256c; + break; + + case 2: + case 4: + cml = cm8_64c; + break; + case 3: + case 5: + cml = cm8_8c; + break; + } + + pixelsSource = + new MemoryImageSource( + fbWidth, + fbHeight, + cml, + pixels8, + 0, + fbWidth); + } else { + pixels8 = null; + pixels24 = new int[fbWidth * fbHeight]; + + pixelsSource = + new MemoryImageSource( + fbWidth, + fbHeight, + cm24, + pixels24, + 0, + fbWidth); + } + pixelsSource.setAnimated(true); + rawPixelsImage = createImage(pixelsSource); + + // Update the size of desktop containers. + if (viewer.inSeparateFrame) { + if (viewer.desktopScrollPane != null) + resizeDesktopFrame(); + } else { + setSize(fbWidth, fbHeight); + } + } + + void resizeDesktopFrame() { + setSize(rfb.framebufferWidth, rfb.framebufferHeight); + + // FIXME: Find a better way to determine correct size of a + // ScrollPane. -- const + Insets insets = viewer.desktopScrollPane.getInsets(); + viewer.desktopScrollPane.setSize( + rfb.framebufferWidth + 2 * Math.min(insets.left, insets.right), + rfb.framebufferHeight + 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; + 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); + + // + // main dispatch loop + // + + while (true) { + // Read message type from the server. + int msgType = rfb.readServerMessageType(); + + // Process the message depending on its type. + switch (msgType) { + case RfbProto.FramebufferUpdate : + rfb.readFramebufferUpdate(); + + for (int i = 0; i < rfb.updateNRects; i++) { + rfb.readFramebufferUpdateRectHdr(); + 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; + } + + switch (rfb.updateRectEncoding) { + case RfbProto.EncodingRaw : + handleRawRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingCopyRect : + 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 : + handleHextileRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingZlib : + handleZlibRect(rx, ry, rw, rh); + break; + case RfbProto.EncodingTight : + handleTightRect(rx, ry, rw, rh); + break; + // marscha - PointerPos + case RfbProto.EncodingPointerPos : + handleCursorPosUpdate(rx, ry); + break; + default : + throw new Exception( + "Unknown RFB rectangle encoding " + + rfb.updateRectEncoding); + } + } + + 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. + if (viewer.deferUpdateRequests > 0) { + synchronized (rfb) { + try { + rfb.wait(viewer.deferUpdateRequests); + } catch (InterruptedException e) { + } + } + } + + // Before requesting framebuffer update, check if the pixel + // format should be changed. If it should, request full update + // instead of an incremental one. + if ((viewer.options.eightBitColors > 0) && (bytesPixel != 1) + || + (viewer.options.eightBitColors == 0) && (bytesPixel == 1) + || + (viewer.options.eightBitColors != viewer.options.oldEightBitColors) + ) + { + setPixelFormat(); + fullUpdateNeeded = true; + } + + rfb.writeFramebufferUpdateRequest( + 0, + 0, + rfb.framebufferWidth, + rfb.framebufferHeight, + !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; + + case RfbProto.rfbFileTransfer : + viewer.rfb.readRfbFileTransferMsg(); + 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.is.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.is.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.is.readInt(); + + byte[] bg_buf = new byte[bytesPixel]; + rfb.is.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.is.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.is.readInt(); + + byte[] bg_buf = new byte[bytesPixel]; + rfb.is.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.is.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.is.readUnsignedByte(); + 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.is.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.is.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.is.readUnsignedByte(); + int bufsize = nSubrects * 2; + if ((subencoding & rfb.HextileSubrectsColoured) != 0) { + bufsize += nSubrects * bytesPixel; + } + byte[] buf = new byte[bufsize]; + rfb.is.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 Zlib-encoded rectangle. + // + + void handleZlibRect(int x, int y, int w, int h) throws Exception { + + int nBytes = rfb.is.readInt(); + + if (zlibBuf == null || zlibBufLen < nBytes) { + zlibBufLen = nBytes * 2; + zlibBuf = new byte[zlibBufLen]; + } + + rfb.is.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.is.readUnsignedByte(); + 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.is.readUnsignedByte(); + memGraphics.setColor(colors[idx]); + if (rfb.rec != null) { + rfb.rec.writeByte(idx); + } + } else { + byte[] buf = new byte[3]; + rfb.is.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) { + + // Read JPEG data. + byte[] jpegData = new byte[rfb.readCompactLen()]; + rfb.is.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.is.readUnsignedByte(); + if (rfb.rec != null) { + rfb.rec.writeByte(filter_id); + } + if (filter_id == rfb.TightFilterPalette) { + numColors = rfb.is.readUnsignedByte() + 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.is.readFully(palette8); + if (rfb.rec != null) { + rfb.rec.write(palette8); + } + } else { + byte[] buf = new byte[numColors * 3]; + rfb.is.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.is.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.is.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.is.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.is.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.is.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. + repaint(viewer.deferScreenUpdates, x, y, w, h); + } + + // + // 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()); + } + 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) { + } + + ////////////////////////////////////////////////////////////////// + // + // Handle cursor shape updates (XCursor and RichCursor encodings). + // + + boolean showSoftCursor = false; + + int[] softCursorPixels; + MemoryImageSource softCursorSource; + Image softCursor; + + int cursorX = 0, cursorY = 0; + int cursorWidth, cursorHeight; + int hotX, hotY; + + // + // Handle cursor shape update (XCursor and RichCursor encodings). + // + + synchronized void handleCursorShapeUpdate( + int encodingType, + int xhot, + int yhot, + int width, + int height) + throws IOException { + + int bytesPerRow = (width + 7) / 8; + int bytesMaskData = bytesPerRow * height; + + softCursorFree(); + + if (width * height == 0) + return; + + // Ignore cursor shape data if requested by user. + + if (viewer.options.ignoreCursorUpdates) { + if (encodingType == rfb.EncodingXCursor) { + rfb.is.skipBytes(6 + bytesMaskData * 2); + } else { + // rfb.EncodingRichCursor + rfb.is.skipBytes(width * height + bytesMaskData); + } + return; + } + + // Decode cursor pixel data. + + softCursorPixels = new int[width * height]; + + if (encodingType == rfb.EncodingXCursor) { + + // Read foreground and background colors of the cursor. + byte[] rgb = new byte[6]; + rfb.is.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.is.readFully(pixBuf); + byte[] maskBuf = new byte[bytesMaskData]; + rfb.is.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.is.readFully(pixBuf); + byte[] maskBuf = new byte[bytesMaskData]; + rfb.is.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 = 0; + // sf@2005 + switch (viewer.options.eightBitColors) + { + case 1: + result = cm8_256c.getRGB(pixBuf[i]); + break; + case 2: + case 4: + result = cm8_64c.getRGB(pixBuf[i]); + break; + case 3: + case 5: + result = cm8_8c.getRGB(pixBuf[i]); + break; + } + } + else + { + result = + 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF) + << 16 | (pixBuf[i * 4 + 2] & 0xFF) + << 8 | (pixBuf[i * 4 + 3] & 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 = 0; +// sf@2005 + switch (viewer.options.eightBitColors) + { + case 1: + result = cm8_256c.getRGB(pixBuf[i]); + break; + case 2: + case 4: + result = cm8_64c.getRGB(pixBuf[i]); + break; + case 3: + case 5: + result = cm8_8c.getRGB(pixBuf[i]); + break; + } } + else + { + result = + 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF) + << 16 | (pixBuf[i * 4 + 2] & 0xFF) + << 8 | (pixBuf[i * 4 + 3] & 0xFF); + } + } else { + result = 0; // Transparent pixel + } + softCursorPixels[i++] = result; + } + } + + } + + // Draw the cursor on an off-screen image. + + softCursorSource = + new MemoryImageSource(width, height, softCursorPixels, 0, width); + softCursor = createImage(softCursorSource); + + // Set remaining data associated with cursor. + + cursorWidth = width; + cursorHeight = height; + hotX = xhot; + hotY = yhot; + + showSoftCursor = true; + + // Show the cursor. + + repaint( + viewer.deferCursorUpdates, + cursorX - hotX, + cursorY - hotY, + cursorWidth, + cursorHeight); + } + + // + // marscha - PointerPos + // Handle cursor position update (PointerPos encoding). + // + + synchronized void handleCursorPosUpdate( + int x, + int y) { + if (x >= rfb.framebufferWidth) + x = rfb.framebufferWidth - 1; + if (y >= rfb.framebufferHeight) + y = rfb.framebufferHeight - 1; + + softCursorMove(x, y); + } + + // + // softCursorMove(). Moves soft cursor into a particular location. + // + + synchronized void softCursorMove(int x, int y) { + if (showSoftCursor) { + repaint( + viewer.deferCursorUpdates, + cursorX - hotX, + cursorY - hotY, + cursorWidth, + cursorHeight); + repaint( + viewer.deferCursorUpdates, + x - hotX, + y - hotY, + cursorWidth, + cursorHeight); + } + + cursorX = x; + cursorY = y; + } + + // + // softCursorFree(). Remove soft cursor, dispose resources. + // + + synchronized void softCursorFree() { + if (showSoftCursor) { + showSoftCursor = false; + softCursor = null; + softCursorSource = null; + softCursorPixels = null; + + repaint( + viewer.deferCursorUpdates, + cursorX - hotX, + cursorY - hotY, + cursorWidth, + cursorHeight); + } + } +} diff --git a/main/app_share/VncViewer.class b/main/app_share/VncViewer.class new file mode 100644 index 0000000000..b542d56f27 Binary files /dev/null and b/main/app_share/VncViewer.class differ diff --git a/main/app_share/VncViewer.jar b/main/app_share/VncViewer.jar new file mode 100644 index 0000000000..15625a128a Binary files /dev/null and b/main/app_share/VncViewer.jar differ diff --git a/main/app_share/VncViewer.java b/main/app_share/VncViewer.java new file mode 100644 index 0000000000..4eee13e388 --- /dev/null +++ b/main/app_share/VncViewer.java @@ -0,0 +1,1020 @@ +// Copyright (C) 2002-2005 Ultr@VNC Team. All Rights Reserved. +// Copyright (C) 2004 Kenn Min Chong, John Witchel. All Rights Reserved. +// Copyright (C) 2004 Alban Chazot. All Rights Reserved. +// Copyright (C) 2001,2002 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. +// + +// Alban Chazot - Carmi Grenoble July 5th 2004 +// * Add support for Ultr@VNC mslogon feature. +// You can now be connected to a Ultr@VNC box with mslogon required. +// Thanks to Wim Vandersmissen, who provide a TightVNC viewer patch do to so. +// That give me the idea to provide it in the java viewer too. +// +// * Add ScrollPanel to applet mode +// +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; +import javax.swing.*; + +public class VncViewer extends java.applet.Applet implements java.lang.Runnable, WindowListener { + + boolean inAnApplet = true; + + boolean inSeparateFrame = false; + + // mslogon support + + boolean mslogon = false; + + // mslogon support end + + // + // 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; + + v.init(); + v.start(); + } + + String[] mainArgs; + + RfbProto rfb; + + Thread rfbThread; + + Frame vncFrame; + + Container vncContainer; + + ScrollPane desktopScrollPane; + + GridBagLayout gridbag; + + ButtonPanel buttonPanel; + + //AuthPanel authenticator; + + VncCanvas vc; + + OptionsFrame options; + + ClipboardFrame clipboard; + + RecordingFrame rec; + + FTPFrame ftp; // KMC: FTP Frame declaration + + // Control session recording. + Object recordingSync; + + String sessionFileName; + + boolean recordingActive; + + boolean recordingStatusChanged; + + String cursorUpdatesDef; + + String eightBitColorsDef; + + // Variables read from parameter values. + String host; + + int port; + + String passwordParam; + + String encPasswordParam; + + boolean showControls; + + boolean showOfflineDesktop; + + int deferScreenUpdates; + + int deferCursorUpdates; + + int deferUpdateRequests; + + String serverID; + + // mslogon support 2 + String usernameParam; + + String encUsernameParam; + + String dm; + + byte[] domain = new byte[256]; + + byte[] user = new byte[256]; + + byte[] passwd = new byte[32]; + + int i; + + // mslogon support 2 end + + // + // init() + // + + public void init() { + + readParameters(); + + if (inSeparateFrame) { + vncFrame = new Frame("Ultr@VNC"); + if (!inAnApplet) { + vncFrame.add("Center", this); + } + vncContainer = vncFrame; + } else { + vncContainer = this; + } + + recordingSync = new Object(); + + options = new OptionsFrame(this); + clipboard = new ClipboardFrame(this); + // authenticator = new AuthPanel(false); // mslogon support : go to connectAndAuthenticate() + if (RecordingFrame.checkSecurity()) + rec = new RecordingFrame(this); + + sessionFileName = null; + recordingActive = false; + recordingStatusChanged = false; + cursorUpdatesDef = null; + eightBitColorsDef = null; + + if (inSeparateFrame) + vncFrame.addWindowListener(this); + + ftp = new FTPFrame(this); // KMC: FTPFrame creation + 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; + + // modif appshare pvandermaesen@noctis.be, never show control + /* + if (showControls) { + buttonPanel = new ButtonPanel(this); + gridbag.setConstraints(buttonPanel, gbc); + vncContainer.add(buttonPanel); + } + */ + + try { + connectAndAuthenticate(); + + doProtocolInitialisation(); + + vc = new VncCanvas(this); + gbc.weightx = 1.0; + gbc.weighty = 1.0; + + // Add ScrollPanel to applet mode + + // 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); + + if (inSeparateFrame) { + // Finally, add our ScrollPane to the Frame window. + vncFrame.add(desktopScrollPane); + vncFrame.setTitle(rfb.desktopName); + vncFrame.pack(); + vc.resizeDesktopFrame(); + } else { + // Finally, add the scrollable panel component to the Applet. + gridbag.setConstraints(desktopScrollPane, gbc); + add(desktopScrollPane); + + // Add ScrollPanel to applet mode end + + validate(); + + } + + if (showControls) + buttonPanel.enableButtons(); + + moveFocusToDesktop(); + vc.processNormalProtocol(); + + } catch (NoRouteToHostException e) { + e.printStackTrace(); + fatalError("Network error: no route to server: " + host); + } catch (UnknownHostException e) { + e.printStackTrace(); + fatalError("Network error: server name unknown: " + host); + } catch (ConnectException e) { + e.printStackTrace(); + fatalError("Network error: could not connect to server: " + host + ":" + port); + } catch (EOFException e) { + e.printStackTrace(); + if (showOfflineDesktop) { + 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.close(); + rfb = null; + } + if (showControls && buttonPanel != null) { + buttonPanel.disableButtonsOnDisconnect(); + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + } + } else { + fatalError("Network error: remote side closed connection"); + } + } catch (IOException e) { + String str = e.getMessage(); + e.printStackTrace(); + if (str != null && str.length() != 0) { + fatalError("Network Error: " + str); + } else { + fatalError(e.toString()); + } + } catch (Exception e) { + String str = e.getMessage(); + e.printStackTrace(); + if (str != null && str.length() != 0) { + fatalError("Error: " + str); + } else { + fatalError(e.toString()); + } + } + + } + + // + // Connect to the RFB server and authenticate the user. + // + + void connectAndAuthenticate() throws Exception { + + // If "ENCPASSWORD" parameter is set, decrypt the password into + // the passwordParam string. + + if (encPasswordParam != null) { + // 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); + } + + // If a password parameter ("PASSWORD" or "ENCPASSWORD") is set, + // don't ask user a password, get one from passwordParam instead. + // Authentication failures would be fatal. + + if (passwordParam != null) { + + if (inSeparateFrame) { + vncFrame.pack(); + vncFrame.show(); + } else { + validate(); + } + + /* modif for autologin for dokeos appshare (noctis) */ + + + /* end modif */ + + if (!tryAuthenticate(usernameParam, passwordParam)) { + throw new Exception("VNC authentication failed"); + } + return; + } + + // There is no "PASSWORD" or "ENCPASSWORD" parameters -- ask user + // for a password, try to authenticate, retry on authentication + // failures. + + // mslogon support + // + // Detect Auth Protocol (Ultr@VNC or the standard One) + // To know if we must show the username box + // + + prologueDetectAuthProtocol(); + + //authenticator = new AuthPanel(mslogon); + + // mslogon support end + + 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(authenticator, gbc); + //vncContainer.add(authenticator); + + if (inSeparateFrame) { + vncFrame.pack(); + vncFrame.show(); + } else { + validate(); + // FIXME: here moveFocusToPasswordField() does not always work + // under Netscape 4.7x/Java 1.1.5/Linux. It seems like this call + // is being executed before the password field of the + // authenticator is fully drawn and activated, therefore + // requestFocus() does not work. Currently, I don't know how to + // solve this problem. + // -- const + + // mslogon support + //authenticator.moveFocusToUsernameField(); + // mslogon support end + } + + while (true) { + // Wait for user entering a password, or a username and a password + +// Try to authenticate with a given password. + // mslogon support + String us=""; + if (mslogon) { + //us = authenticator.username.getText(); + } else { + us = ""; + } + + /* add for dokeos appShare (noctis) */ + if (tryAuthenticate(us, "dokeos")) + break; + + + + } + + + } + + // mslogon support + // + // Detect Server rfb Protocol to know the auth Method + // Perform a connexion to detect the Serverminor + // + + void prologueDetectAuthProtocol() throws Exception { + + rfb = new RfbProto(host, port, this, serverID); + + rfb.readVersionMsg(); + + System.out.println("RFB server supports protocol version " + rfb.serverMajor + "." + rfb.serverMinor); + + // Mslogon support + if (rfb.serverMinor == 4) { + mslogon = true; + System.out.println("Ultr@VNC mslogon detected"); + } + + rfb.writeVersionMsg(); + + } + + // mslogon support end + + // + // Try to authenticate with a given password. + // + + boolean tryAuthenticate(String us, String pw) throws Exception { + + rfb = new RfbProto(host, port, this, serverID); + + rfb.readVersionMsg(); + + System.out.println("RFB server supports protocol version " + rfb.serverMajor + "." + rfb.serverMinor); + + rfb.writeVersionMsg(); + + int authScheme = rfb.readAuthScheme(); + + switch (authScheme) { + + case RfbProto.NoAuth: + System.out.println("No authentication needed"); + return true; + + case RfbProto.VncAuth: + + if (mslogon) { + System.out.println("showing JOptionPane warning."); + int n = JOptionPane.showConfirmDialog(vncFrame, "The current authentication method does not transfer your password securely." + "Do you want to continue?", "Warning", + JOptionPane.YES_NO_OPTION); + if (n != JOptionPane.YES_OPTION) { + throw new Exception("User cancelled insecure MS-Logon"); + } + } + // mslogon support + byte[] challengems = new byte[64]; + if (mslogon) { + // copy the us (user) parameter into the user Byte formated variable + System.arraycopy(us.getBytes(), 0, user, 0, us.length()); + // and pad it with Null + if (us.length() < 256) { + for (i = us.length(); i < 256; i++) { + user[i] = 0; + } + } + + dm = "."; + // copy the dm (domain) parameter into the domain Byte formated variable + System.arraycopy(dm.getBytes(), 0, domain, 0, dm.length()); + // and pad it with Null + if (dm.length() < 256) { + for (i = dm.length(); i < 256; i++) { + domain[i] = 0; + } + } + + // equivalent of vncEncryptPasswdMS + + // copy the pw (password) parameter into the password Byte formated variable + System.arraycopy(pw.getBytes(), 0, passwd, 0, pw.length()); + // and pad it with Null + if (pw.length() < 32) { + for (i = pw.length(); i < 32; i++) { + passwd[i] = 0; + } + } + + // Encrypt the full given password + byte[] fixedkey = { 23, 82, 107, 6, 35, 78, 88, 7 }; + DesCipher desme = new DesCipher(fixedkey); + desme.encrypt(passwd, 0, passwd, 0); + + // end equivalent of vncEncryptPasswdMS + + // get the mslogon Challenge from server + rfb.is.readFully(challengems); + } + // mslogon support end + + byte[] challenge = new byte[16]; + rfb.is.readFully(challenge); + + if (pw.length() > 8) + pw = pw.substring(0, 8); // Truncate to 8 chars + + // vncEncryptBytes in the UNIX libvncauth truncates password + // after the first zero byte. We do to. + int firstZero = pw.indexOf(0); + if (firstZero != -1) + pw = pw.substring(0, firstZero); + + // mslogon support + if (mslogon) { + for (i = 0; i < 32; i++) { + challengems[i] = (byte) (passwd[i] ^ challengems[i]); + } + rfb.os.write(user); + rfb.os.write(domain); + rfb.os.write(challengems); + } + // mslogon support end + + 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); + + rfb.os.write(challenge); + + int authResult = rfb.is.readInt(); + + switch (authResult) { + case RfbProto.VncAuthOK: + System.out.println("VNC authentication succeeded"); + return true; + case RfbProto.VncAuthFailed: + System.out.println("VNC authentication failed"); + break; + case RfbProto.VncAuthTooMany: + throw new Exception("VNC authentication failed - too many tries"); + default: + throw new Exception("Unknown VNC authentication result " + authResult); + } + break; + + case RfbProto.MsLogon: + System.out.println("MS-Logon (DH) detected"); + if (AuthMsLogon(us, pw)) { + return true; + } + break; + default: + throw new Exception("Unknown VNC authentication scheme " + authScheme); + } + return false; + } + + // marscha@2006: Try to better hide the windows password. + // I know that this is no breakthrough in modern cryptography. + // It's just a patch/kludge/workaround. + boolean AuthMsLogon(String us, String pw) throws Exception { + byte user[] = new byte[256]; + byte passwd[] = new byte[64]; + + long gen = rfb.is.readLong(); + long mod = rfb.is.readLong(); + long resp = rfb.is.readLong(); + + DH dh = new DH(gen, mod); + long pub = dh.createInterKey(); + + rfb.os.write(DH.longToBytes(pub)); + + long key = dh.createEncryptionKey(resp); + System.out.println("gen=" + gen + ", mod=" + mod + ", pub=" + pub + ", key=" + key); + + DesCipher des = new DesCipher(DH.longToBytes(key)); + + System.arraycopy(us.getBytes(), 0, user, 0, us.length()); + // and pad it with Null + if (us.length() < 256) { + for (i = us.length(); i < 256; i++) { + user[i] = 0; + } + } + + // copy the pw (password) parameter into the password Byte formated variable + System.arraycopy(pw.getBytes(), 0, passwd, 0, pw.length()); + // and pad it with Null + if (pw.length() < 32) { + for (i = pw.length(); i < 32; i++) { + passwd[i] = 0; + } + } + + // user = domain + "\\" + user; + + des.encryptText(user, user, DH.longToBytes(key)); + des.encryptText(passwd, passwd, DH.longToBytes(key)); + + rfb.os.write(user); + rfb.os.write(passwd); + + int authResult = rfb.is.readInt(); + + switch (authResult) { + case RfbProto.VncAuthOK: + System.out.println("MS-Logon (DH) authentication succeeded"); + return true; + case RfbProto.VncAuthFailed: + System.out.println("MS-Logon (DH) authentication failed"); + break; + case RfbProto.VncAuthTooMany: + throw new Exception("MS-Logon (DH) authentication failed - too many tries"); + default: + throw new Exception("Unknown MS-Logon (DH) authentication result " + authResult); + } + return false; + } + + // + // 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(); + } + + // + // Send current encoding list to the RFB server. + // + + void setEncodings() { + try { + if (rfb != null && rfb.inNormalProtocol) { + rfb.writeSetEncodings(options.encodings, options.nEncodings); + if (vc != null) { + vc.softCursorFree(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // + // 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("Full"); + 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. + // + + public void readParameters() { + host = readParameter("HOST", !inAnApplet); + if (host == null) { + host = getCodeBase().getHost(); + if (host.equals("")) { + fatalError("HOST parameter not specified"); + } + } + + serverID = readParameter("SERVERID", true); + + String str = readParameter("PORT", true); + port = Integer.parseInt(str); + + if (inAnApplet) { + str = readParameter("Open New Window", false); + if (str != null && str.equalsIgnoreCase("Yes")) + inSeparateFrame = true; + } + + encPasswordParam = readParameter("ENCPASSWORD", false); + if (encPasswordParam == null) + passwordParam = readParameter("PASSWORD", false); + + // "Show Controls" set to "No" disables button panel. + showControls = true; + str = readParameter("Show Controls", false); + if (str != null && str.equalsIgnoreCase("No")) + showControls = 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", 50); + } + + 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 the + // VncCanvas or to the AuthPanel. + // + + void moveFocusToDesktop() { + if (vncContainer != null) { + if (vc != null && vncContainer.isAncestorOf(vc)) { + vc.requestFocus(); + } + } + } + + // + // disconnect() - close connection to server. + // + + boolean disconnectRequested = false; + + synchronized public void disconnect() { + disconnectRequested = true; + if (rfb != null) { + rfb.close(); + rfb = null; + } + System.out.println("Disconnect"); + options.dispose(); + clipboard.dispose(); + if (rec != null) + rec.dispose(); + + if (inAnApplet) { + vncContainer.removeAll(); + Label errLabel = new Label("Disconnected"); + errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); + vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30)); + vncContainer.add(errLabel); + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + rfbThread.stop(); + } else { + System.exit(0); + } + } + + // + // fatalError() - print out a fatal error message. + // + + synchronized public void fatalError(String str) { + if (rfb != null) { + rfb.close(); + rfb = null; + } + System.out.println(str); + + if (disconnectRequested) { + // Not necessary to show error message if the error was caused + // by I/O problems after the disconnect() method call. + disconnectRequested = false; + return; + } + + if (inAnApplet) { + vncContainer.removeAll(); + Label errLabel = new Label(str); + errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); + vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30)); + vncContainer.add(errLabel); + if (inSeparateFrame) { + vncFrame.pack(); + } else { + validate(); + } + Thread.currentThread().stop(); + } else { + System.exit(1); + } + } + + // + // This method is called before the applet is destroyed. + // + + public void destroy() { + vncContainer.removeAll(); + options.dispose(); + clipboard.dispose(); + if (ftp != null) + ftp.dispose(); + if (rec != null) + rec.dispose(); + if (rfb != null) + rfb.close(); + if (inSeparateFrame) + vncFrame.dispose(); + } + + // + // Close application properly on window close event. + // + + public void windowClosing(WindowEvent evt) { + if (rfb != null) + disconnect(); + + vncFrame.dispose(); + if (!inAnApplet) { + System.exit(0); + } + } + + // + // Move the keyboard focus to the password field on window activation. + // + + public void windowActivated(WindowEvent evt) { + } + + // + // Ignore window events we're not interested in. + // + + 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 --git a/main/app_share/appshare.php b/main/app_share/appshare.php new file mode 100644 index 0000000000..2419b7214b --- /dev/null +++ b/main/app_share/appshare.php @@ -0,0 +1,137 @@ +'.$app_base_file.' not found.
'); + } else { + $source = fopen($app_base_file, "r"); + $target = fopen($app_share_app_file, "a" ); + + $specialCode = rand(100000,999999).time().rand(100000,999999); + $contents = fread ($source, filesize ($app_base_file)); + fwrite ($target, $contents, filesize ($app_base_file)); + fwrite ($target, $specialCode, filesize ($app_base_file)); + fclose($source); + fclose($target); + } + +} else { + $source = fopen($app_share_app_file, "r" ); + fread ($source, filesize ($app_base_file)); // skip binary content + $serverID = fread ($source, filesize($app_share_app_file)-filesize ($app_base_file)); + fclose($source); +} + +if ($_GET["client"] == 'true') { + ?> + + [test viewApplet appShare] + + + + + + + + + + + + + + + + + + + + + + + +

prérequis pour DokeosAppShare :

+ +
+ +
+ +
+ + +
+ diff --git a/main/app_share/dir.mk b/main/app_share/dir.mk new file mode 100644 index 0000000000..2174405661 --- /dev/null +++ b/main/app_share/dir.mk @@ -0,0 +1,20 @@ +# +# Making the VNC applet. +# + +CLASSES = VncViewer.class RfbProto.class AuthPanel.class VncCanvas.class \ + OptionsFrame.class ClipboardFrame.class ButtonPanel.class \ + DesCipher.class + +PAGES = index.vnc shared.vnc noshared.vnc hextile.vnc zlib.vnc tight.vnc + +all: $(CLASSES) VncViewer.jar + +VncViewer.jar: $(CLASSES) + @$(JavaArchive) + +export:: $(CLASSES) VncViewer.jar $(PAGES) + @$(ExportJavaClasses) + +clean:: + $(RM) *.class *.jar diff --git a/main/app_share/hextile.vnc b/main/app_share/hextile.vnc new file mode 100644 index 0000000000..a8001d5216 --- /dev/null +++ b/main/app_share/hextile.vnc @@ -0,0 +1,19 @@ + + + + +$USER's $DESKTOP desktop ($DISPLAY) + + + + + +
+UltraVNC Site + diff --git a/main/app_share/index.html b/main/app_share/index.html new file mode 100644 index 0000000000..43872566fd --- /dev/null +++ b/main/app_share/index.html @@ -0,0 +1,36 @@ + + [test viewApplet] + + + + + + + + + + + + + + + + + + + + + diff --git a/main/app_share/index.php b/main/app_share/index.php new file mode 100644 index 0000000000..cc1f039053 --- /dev/null +++ b/main/app_share/index.php @@ -0,0 +1,151 @@ +'.$app_base_file.' not found.
'); + } else { + $source = fopen($app_base_file, "r"); + $target = fopen($app_share_app_file, "a" ); + + $specialCode = rand(100000,999999).time().rand(100000,999999); + $contents = fread ($source, filesize ($app_base_file)); + fwrite ($target, $contents, filesize ($app_base_file)); + fwrite ($target, $specialCode, filesize ($app_base_file)); + fclose($source); + fclose($target); + } + +} else { + $source = fopen($app_share_app_file, "r" ); + fread ($source, filesize ($app_base_file)); // skip binary content + $serverID = fread ($source, filesize($app_share_app_file)-filesize ($app_base_file)); + fclose($source); +} + +/* +============================================================================== + HEADER +============================================================================== +*/ +Display :: display_header("appShare"); + +if ($_GET["client"] == 'true') { + ?> + + [test viewApplet appShare] + + + + + + + + + + + + + + + + + + + + + + + +

prérequis pour DokeosAppShare :

+ +
+ +
+ +
+ + +
+ diff --git a/main/app_share/index.vnc b/main/app_share/index.vnc new file mode 100644 index 0000000000..67f13a513e --- /dev/null +++ b/main/app_share/index.vnc @@ -0,0 +1,16 @@ + + + + +$USER's $DESKTOP desktop ($DISPLAY) + + + + +
+UltraVNC Site diff --git a/main/app_share/java.policy.applet b/main/app_share/java.policy.applet new file mode 100644 index 0000000000..ba9f51db46 --- /dev/null +++ b/main/app_share/java.policy.applet @@ -0,0 +1,7 @@ +/* AUTOMATICALLY GENERATED ON Tue Apr 16 17:20:59 EDT 2002*/ +/* DO NOT EDIT */ + +grant { + permission java.security.AllPermission; +}; + diff --git a/main/app_share/mk.bat b/main/app_share/mk.bat new file mode 100644 index 0000000000..6a6e64897f --- /dev/null +++ b/main/app_share/mk.bat @@ -0,0 +1,2 @@ +javac.exe *.java +jar.exe cf VncViewer.jar *.class diff --git a/main/app_share/noshared.vnc b/main/app_share/noshared.vnc new file mode 100644 index 0000000000..e76ff6c987 --- /dev/null +++ b/main/app_share/noshared.vnc @@ -0,0 +1,17 @@ + + + + +$USER's $DESKTOP desktop ($DISPLAY) [not shared] + + + + + +
+UltraVNC Site + diff --git a/main/app_share/plugin.php b/main/app_share/plugin.php new file mode 100644 index 0000000000..e1997a409c --- /dev/null +++ b/main/app_share/plugin.php @@ -0,0 +1,17 @@ + diff --git a/main/app_share/relay/be/noctis/vnc/relay/IProtocolCommand.class b/main/app_share/relay/be/noctis/vnc/relay/IProtocolCommand.class new file mode 100644 index 0000000000..f25ef6c17b Binary files /dev/null and b/main/app_share/relay/be/noctis/vnc/relay/IProtocolCommand.class differ diff --git a/main/app_share/relay/be/noctis/vnc/relay/ProtocolBean.class b/main/app_share/relay/be/noctis/vnc/relay/ProtocolBean.class new file mode 100644 index 0000000000..3a3e5e63a2 Binary files /dev/null and b/main/app_share/relay/be/noctis/vnc/relay/ProtocolBean.class differ diff --git a/main/app_share/relay/be/noctis/vnc/relay/Relay$ClientThread.class b/main/app_share/relay/be/noctis/vnc/relay/Relay$ClientThread.class new file mode 100644 index 0000000000..94c38addac Binary files /dev/null and b/main/app_share/relay/be/noctis/vnc/relay/Relay$ClientThread.class differ diff --git a/main/app_share/relay/be/noctis/vnc/relay/Relay$ConnectClientThread.class b/main/app_share/relay/be/noctis/vnc/relay/Relay$ConnectClientThread.class new file mode 100644 index 0000000000..ef467a9fd8 Binary files /dev/null and b/main/app_share/relay/be/noctis/vnc/relay/Relay$ConnectClientThread.class differ diff --git a/main/app_share/relay/be/noctis/vnc/relay/Relay$RelayThread.class b/main/app_share/relay/be/noctis/vnc/relay/Relay$RelayThread.class new file mode 100644 index 0000000000..bd96c9b0d8 Binary files /dev/null and b/main/app_share/relay/be/noctis/vnc/relay/Relay$RelayThread.class differ diff --git a/main/app_share/relay/be/noctis/vnc/relay/Relay.class b/main/app_share/relay/be/noctis/vnc/relay/Relay.class new file mode 100644 index 0000000000..00a1a57e57 Binary files /dev/null and b/main/app_share/relay/be/noctis/vnc/relay/Relay.class differ diff --git a/main/app_share/run.bat b/main/app_share/run.bat new file mode 100644 index 0000000000..f1edd0e37a --- /dev/null +++ b/main/app_share/run.bat @@ -0,0 +1 @@ +java.exe -cp "s:\Ultravnc.sam\JavaViewer FT" VncViewer HOST localhost PORT 5900 diff --git a/main/app_share/runapplet.bat b/main/app_share/runapplet.bat new file mode 100644 index 0000000000..1955924c2a --- /dev/null +++ b/main/app_share/runapplet.bat @@ -0,0 +1,2 @@ +appletviewer.exe -debug vncviewer.jar + diff --git a/main/app_share/screenshot.jpg b/main/app_share/screenshot.jpg new file mode 100644 index 0000000000..c073612efa Binary files /dev/null and b/main/app_share/screenshot.jpg differ diff --git a/main/app_share/shared.vnc b/main/app_share/shared.vnc new file mode 100644 index 0000000000..abb25a942d --- /dev/null +++ b/main/app_share/shared.vnc @@ -0,0 +1,17 @@ + + + + +$USER's $DESKTOP desktop ($DISPLAY) [shared] + + + + + +
+UltraVNC Site + diff --git a/main/app_share/tight.vnc b/main/app_share/tight.vnc new file mode 100644 index 0000000000..0dd08f656e --- /dev/null +++ b/main/app_share/tight.vnc @@ -0,0 +1,18 @@ + + + + +$USER's $DESKTOP desktop ($DISPLAY) + + + + + +
+UltraVNC Site + diff --git a/main/app_share/zlib.vnc b/main/app_share/zlib.vnc new file mode 100644 index 0000000000..f4b2f160dd --- /dev/null +++ b/main/app_share/zlib.vnc @@ -0,0 +1,18 @@ + + + + +$USER's $DESKTOP desktop ($DISPLAY) + + + + + +
+UltraVNC Site +