mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
1022 lines
27 KiB
Java
1022 lines
27 KiB
Java
//
|
|
// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved.
|
|
// Copyright (C) 2002 Constantin Kaplinsky. All Rights Reserved.
|
|
// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
|
|
//
|
|
// This is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This software is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this software; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
// USA.
|
|
//
|
|
|
|
//
|
|
// VncViewer.java - the VNC viewer applet. This class mainly just sets up the
|
|
// user interface, leaving it to the VncCanvas to do the actual rendering of
|
|
// a VNC desktop.
|
|
//
|
|
|
|
import java.applet.Applet;
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.MemoryImageSource;
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.util.Date;
|
|
|
|
public class ConsoleViewer implements java.lang.Runnable {
|
|
|
|
int id = getNextId();
|
|
boolean compressServerMessage = false;
|
|
long createTime = System.currentTimeMillis();
|
|
long lastUsedTime = System.currentTimeMillis();
|
|
|
|
boolean dropMe = false;
|
|
|
|
OutputStream clientStream;
|
|
String clientStreamInfo;
|
|
|
|
int status;
|
|
public final static int STATUS_ERROR = -1;
|
|
public final static int STATUS_UNINITIALIZED = 0;
|
|
public final static int STATUS_CONNECTING = 1;
|
|
public final static int STATUS_INITIALIZING = 2;
|
|
public final static int STATUS_NORMAL_OPERATION = 3;
|
|
public final static int STATUS_AUTHENTICATION_FAILURE = 100;
|
|
|
|
boolean inAnApplet = true;
|
|
boolean inSeparateFrame = false;
|
|
boolean inProxyMode = false;
|
|
|
|
String[] mainArgs;
|
|
|
|
RfbProto rfb;
|
|
Thread rfbThread;
|
|
|
|
ConsoleApplet applet;
|
|
Frame vncFrame;
|
|
Container vncContainer;
|
|
ScrollPane desktopScrollPane;
|
|
GridBagLayout gridbag;
|
|
ButtonPanel buttonPanel;
|
|
Label connStatusLabel;
|
|
ConsoleCanvas vc;
|
|
OptionsFrame options;
|
|
ClipboardFrame clipboard;
|
|
private RecordingFrame rec;
|
|
|
|
// Control session recording.
|
|
Object recordingSync;
|
|
String sessionFileName;
|
|
boolean recordingActive;
|
|
boolean recordingStatusChanged;
|
|
String cursorUpdatesDef;
|
|
String eightBitColorsDef;
|
|
|
|
// Variables read from parameter values.
|
|
String socketFactory;
|
|
String host;
|
|
int port;
|
|
String proxyHost;
|
|
int proxyPort;
|
|
String passwordParam;
|
|
boolean showControls;
|
|
boolean offerRelogin;
|
|
boolean showOfflineDesktop;
|
|
int deferScreenUpdates;
|
|
int deferCursorUpdates;
|
|
int deferUpdateRequests;
|
|
|
|
static int id_count = 1;
|
|
synchronized static int getNextId() {
|
|
return id_count++;
|
|
}
|
|
|
|
//
|
|
// init()
|
|
//
|
|
|
|
public void init() {
|
|
if (inProxyMode) {
|
|
initProxy();
|
|
return;
|
|
}
|
|
readParameters();
|
|
|
|
if (inSeparateFrame) {
|
|
vncFrame = new Frame("VMOps ConsoleViewer");
|
|
if (!inAnApplet) {
|
|
// vncFrame.add("Center", this);
|
|
}
|
|
vncContainer = vncFrame;
|
|
} else {
|
|
vncContainer = applet;
|
|
}
|
|
|
|
recordingSync = new Object();
|
|
|
|
options = new OptionsFrame(this);
|
|
// clipboard = new ClipboardFrame(this);
|
|
// if (RecordingFrame.checkSecurity())
|
|
// rec = new RecordingFrame(this);
|
|
|
|
sessionFileName = null;
|
|
recordingActive = false;
|
|
recordingStatusChanged = false;
|
|
cursorUpdatesDef = null;
|
|
eightBitColorsDef = null;
|
|
|
|
if (inSeparateFrame)
|
|
vncFrame.addWindowListener(applet);
|
|
rfbThread = new Thread(this);
|
|
rfbThread.start();
|
|
}
|
|
|
|
private void initProxy() {
|
|
recordingSync = new Object();
|
|
|
|
options = new OptionsFrame(this);
|
|
options.viewOnly = true;
|
|
|
|
sessionFileName = null;
|
|
recordingActive = false;
|
|
recordingStatusChanged = false;
|
|
cursorUpdatesDef = null;
|
|
eightBitColorsDef = null;
|
|
|
|
rfbThread = new Thread(this);
|
|
rfbThread.setName("RFB Thread " + rfbThread.getId() + " >" + host + ":"
|
|
+ port);
|
|
|
|
rfbThread.start();
|
|
}
|
|
|
|
public void update(Graphics g) {
|
|
}
|
|
|
|
//
|
|
// run() - executed by the rfbThread to deal with the RFB socket.
|
|
//
|
|
|
|
public void run() {
|
|
if (inProxyMode) {
|
|
runProxy();
|
|
return;
|
|
}
|
|
|
|
int[] pixels = new int[16 * 16];
|
|
Image image = Toolkit.getDefaultToolkit().createImage(
|
|
new MemoryImageSource(16, 16, pixels, 0, 16));
|
|
Cursor transparentCursor =
|
|
Toolkit.getDefaultToolkit().createCustomCursor
|
|
(image, new Point(0, 0), "invisibleCursor");
|
|
vncContainer.setCursor(transparentCursor);
|
|
|
|
|
|
gridbag = new GridBagLayout();
|
|
vncContainer.setLayout(gridbag);
|
|
GridBagConstraints gbc = new GridBagConstraints();
|
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
|
gbc.anchor = GridBagConstraints.NORTHWEST;
|
|
//gbc.anchor = GridBagConstraints.CENTER;
|
|
|
|
if (showControls) {
|
|
buttonPanel = new ButtonPanel(this);
|
|
gridbag.setConstraints(buttonPanel, gbc);
|
|
vncContainer.add(buttonPanel);
|
|
}
|
|
|
|
// FIXME: Use auto-scaling not only in a separate frame.
|
|
if (options.autoScale && inSeparateFrame) {
|
|
Dimension screenSize;
|
|
try {
|
|
screenSize = vncContainer.getToolkit().getScreenSize();
|
|
} catch (Exception e) {
|
|
screenSize = new Dimension(0, 0);
|
|
}
|
|
createCanvas(screenSize.width - 32, screenSize.height - 32);
|
|
} else {
|
|
createCanvas(0, 0);
|
|
}
|
|
|
|
gbc.weightx = 1.0;
|
|
gbc.weighty = 1.0;
|
|
|
|
if (inSeparateFrame) {
|
|
|
|
// Create a panel which itself is resizeable and can hold
|
|
// non-resizeable VncCanvas component at the top left corner.
|
|
Panel canvasPanel = new Panel();
|
|
canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
|
canvasPanel.add(vc);
|
|
|
|
// Create a ScrollPane which will hold a panel with VncCanvas
|
|
// inside.
|
|
desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
|
|
gbc.fill = GridBagConstraints.BOTH;
|
|
gridbag.setConstraints(desktopScrollPane, gbc);
|
|
desktopScrollPane.add(canvasPanel);
|
|
// Finally, add our ScrollPane to the Frame window.
|
|
vncFrame.add(desktopScrollPane);
|
|
// vncFrame.setTitle(rfb.desktopName);
|
|
vncFrame.pack();
|
|
vc.resizeDesktopFrame();
|
|
} else {
|
|
gridbag.setConstraints(vc, gbc);
|
|
applet.add(vc);
|
|
applet.validate();
|
|
}
|
|
|
|
if (showControls)
|
|
buttonPanel.enableButtons();
|
|
moveFocusToDesktop();
|
|
|
|
try {
|
|
connectAndAuthenticate();
|
|
doProtocolInitialisation();
|
|
vc.rfb = rfb;
|
|
vc.setPixelFormat();
|
|
vc.rfb.writeFramebufferUpdateRequest(0, 0, vc.rfb.framebufferWidth,
|
|
vc.rfb.framebufferHeight, false);
|
|
vc.processNormalProtocol();
|
|
// We should never get here, but just in case
|
|
showMessage("Disconnected from server");
|
|
} catch (NoRouteToHostException e) {
|
|
fatalError("Network error: no route to server: " + host, e);
|
|
} catch (UnknownHostException e) {
|
|
fatalError("Network error: server name unknown: " + host, e);
|
|
} catch (ConnectException e) {
|
|
fatalError("Network error: could not connect to server: " + host
|
|
+ ":" + port, e);
|
|
} catch (EOFException e) {
|
|
fatalError("Network error: remote side closed connection", e);
|
|
} catch (IOException e) {
|
|
fatalError(e.getMessage(), e);
|
|
} catch (Exception e) {
|
|
fatalError(e.getMessage(), e);
|
|
} finally {
|
|
encodingsSaved = null;
|
|
nEncodingsSaved = 0;
|
|
synchronized (this) {
|
|
if (rfb != null) {
|
|
rfb.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void runProxy() {
|
|
createCanvas(0, 0);
|
|
|
|
int delay = 0;
|
|
while (!dropMe) {
|
|
try {
|
|
status = STATUS_CONNECTING;
|
|
connectAndAuthenticate();
|
|
delay = 0; // reset the delay interval
|
|
status = STATUS_INITIALIZING;
|
|
doProtocolInitialisation();
|
|
vc.rfb = rfb;
|
|
vc.setPixelFormat();
|
|
vc.rfb.writeFramebufferUpdateRequest(0, 0,
|
|
vc.rfb.framebufferWidth, vc.rfb.framebufferHeight,
|
|
false);
|
|
status = STATUS_NORMAL_OPERATION;
|
|
vc.processNormalProtocol();
|
|
} catch (AuthenticationException e) {
|
|
status = STATUS_AUTHENTICATION_FAILURE;
|
|
String msg = e.getMessage();
|
|
Logger.log(Logger.INFO, msg);
|
|
} catch (Exception e) {
|
|
status = STATUS_ERROR;
|
|
Logger.log(Logger.INFO, e.toString());
|
|
} finally {
|
|
String oldName = Thread.currentThread().getName();
|
|
encodingsSaved = null;
|
|
nEncodingsSaved = 0;
|
|
synchronized (this) {
|
|
if (rfb != null) {
|
|
rfb.close();
|
|
}
|
|
}
|
|
}
|
|
if (dropMe) {
|
|
break;
|
|
}
|
|
if (status == STATUS_AUTHENTICATION_FAILURE) {
|
|
break;
|
|
} else {
|
|
Logger.log(Logger.INFO, "Exception caught, retrying in "
|
|
+ delay + "ms");
|
|
try {
|
|
Thread.sleep(delay);
|
|
} catch (InterruptedException e) {
|
|
// ignored
|
|
}
|
|
delay = (int) ((float) (delay + 700) * 1.5);
|
|
if (delay > 3000) {
|
|
Logger.log(Logger.INFO, "Delay value gets too large " + delay + ", stop retrying");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Logger.log(Logger.INFO, "RFB thread terminating");
|
|
}
|
|
|
|
//
|
|
// Create a VncCanvas instance.
|
|
//
|
|
|
|
void createCanvas(int maxWidth, int maxHeight) {
|
|
/*
|
|
* // Determine if Java 2D API is available and use a special // version
|
|
* of VncCanvas if it is present. vc = null; try { // This throws
|
|
* ClassNotFoundException if there is no Java 2D API. Class cl =
|
|
* Class.forName("java.awt.Graphics2D"); // If we could load Graphics2D
|
|
* class, then we can use VncCanvas2D. cl = Class.forName("VncCanvas2");
|
|
* Class[] argClasses = { this.getClass(), Integer.TYPE, Integer.TYPE };
|
|
* java.lang.reflect.Constructor cstr = cl.getConstructor(argClasses);
|
|
* Object[] argObjects = { this, new Integer(maxWidth), new
|
|
* Integer(maxHeight) }; vc = (VncCanvas)cstr.newInstance(argObjects); }
|
|
* catch (Exception e) { Logger.log(Logger.INFO,
|
|
* "Warning: Java 2D API is not available"); }
|
|
*
|
|
* // If we failed to create VncCanvas2D, use old VncCanvas. if (vc ==
|
|
* null)
|
|
*/
|
|
vc = new ConsoleCanvas2(this, maxWidth, maxHeight);
|
|
}
|
|
|
|
/*
|
|
* // // Process RFB socket messages. // If the rfbThread is being stopped,
|
|
* ignore any exceptions, // otherwise rethrow the exception so it can be
|
|
* handled. //
|
|
*
|
|
* void processNormalProtocol() throws Exception { try {
|
|
* vc.processNormalProtocol(); } catch (Exception e) { if (rfbThread ==
|
|
* null) { Logger.log(Logger.INFO, "Ignoring RFB socket exceptions" +
|
|
* " because applet is stopping"); } else { throw e; } } }
|
|
*/
|
|
//
|
|
// Connect to the RFB server and authenticate the user.
|
|
//
|
|
void connectAndAuthenticate() throws Exception {
|
|
showConnectionStatus("Initializing...");
|
|
if (!inProxyMode) {
|
|
if (inSeparateFrame) {
|
|
vncFrame.pack();
|
|
vncFrame.show();
|
|
} else {
|
|
applet.validate();
|
|
}
|
|
}
|
|
|
|
if (proxyHost != null) {
|
|
showConnectionStatus("Connecting to " + proxyHost + ", port "
|
|
+ proxyPort + "...");
|
|
rfb = new RfbProto(proxyHost, proxyPort, this);
|
|
rfb.readProxyVersion();
|
|
rfb.writeProxyString(host, port,
|
|
(passwordParam != null) ? passwordParam : "");
|
|
} else {
|
|
showConnectionStatus("Connecting to " + host + ", port " + port
|
|
+ "...");
|
|
rfb = new RfbProto(host, port, this);
|
|
}
|
|
showConnectionStatus("Connected to server");
|
|
|
|
rfb.readVersionMsg();
|
|
showConnectionStatus("RFB server supports protocol version "
|
|
+ rfb.serverMajor + "." + rfb.serverMinor);
|
|
|
|
rfb.writeVersionMsg();
|
|
showConnectionStatus("Using RFB protocol version " + rfb.clientMajor
|
|
+ "." + rfb.clientMinor);
|
|
|
|
int secType = rfb.negotiateSecurity();
|
|
int authType;
|
|
if (secType == RfbProto.SecTypeTight) {
|
|
showConnectionStatus("Enabling TightVNC protocol extensions");
|
|
rfb.initCapabilities();
|
|
rfb.setupTunneling();
|
|
authType = rfb.negotiateAuthenticationTight();
|
|
} else {
|
|
authType = secType;
|
|
}
|
|
|
|
switch (authType) {
|
|
case RfbProto.AuthNone:
|
|
showConnectionStatus("No authentication needed");
|
|
rfb.authenticateNone();
|
|
break;
|
|
case RfbProto.AuthVNC:
|
|
showConnectionStatus("Performing standard VNC authentication");
|
|
if (passwordParam != null) {
|
|
rfb.authenticateVNC(passwordParam);
|
|
} else {
|
|
throw new AuthenticationException("Bad password");
|
|
/*
|
|
* String pw = askPassword(); rfb.authenticateVNC(pw);
|
|
*/
|
|
}
|
|
break;
|
|
default:
|
|
throw new Exception("Unknown authentication scheme " + authType);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Show a message describing the connection status.
|
|
// To hide the connection status label, use (msg == null).
|
|
//
|
|
|
|
void showConnectionStatus(String msg) {
|
|
if (inProxyMode) {
|
|
if (msg != null) {
|
|
Logger.log(Logger.INFO, msg);
|
|
}
|
|
return;
|
|
}
|
|
/*
|
|
* if (msg == null) { if (vncContainer.isAncestorOf(connStatusLabel)) {
|
|
* vncContainer.remove(connStatusLabel); } return; }
|
|
*
|
|
* Logger.log(Logger.INFO, msg);
|
|
*
|
|
* if (connStatusLabel == null) { connStatusLabel = new Label("Status: "
|
|
* + msg); connStatusLabel.setFont(new Font("Helvetica", Font.PLAIN,
|
|
* 12)); } else { connStatusLabel.setText("Status: " + msg); }
|
|
*
|
|
* if (!vncContainer.isAncestorOf(connStatusLabel)) { GridBagConstraints
|
|
* gbc = new GridBagConstraints(); gbc.gridwidth =
|
|
* GridBagConstraints.REMAINDER; gbc.fill =
|
|
* GridBagConstraints.HORIZONTAL; gbc.anchor =
|
|
* GridBagConstraints.NORTHWEST; gbc.weightx = 1.0; gbc.weighty = 1.0;
|
|
* gbc.insets = new Insets(20, 30, 20, 30);
|
|
* gridbag.setConstraints(connStatusLabel, gbc);
|
|
* vncContainer.add(connStatusLabel); }
|
|
*
|
|
* if (inSeparateFrame) { vncFrame.pack(); } else { validate(); }
|
|
*/
|
|
}
|
|
|
|
//
|
|
// Show an authentication panel.
|
|
//
|
|
/*
|
|
String askPassword() throws Exception {
|
|
showConnectionStatus(null);
|
|
|
|
AuthPanel authPanel = new AuthPanel(this);
|
|
|
|
GridBagConstraints gbc = new GridBagConstraints();
|
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
|
gbc.anchor = GridBagConstraints.NORTHWEST;
|
|
gbc.weightx = 1.0;
|
|
gbc.weighty = 1.0;
|
|
gbc.ipadx = 100;
|
|
gbc.ipady = 50;
|
|
gridbag.setConstraints(authPanel, gbc);
|
|
vncContainer.add(authPanel);
|
|
|
|
if (inSeparateFrame) {
|
|
vncFrame.pack();
|
|
} else {
|
|
applet.validate();
|
|
}
|
|
|
|
authPanel.moveFocusToDefaultField();
|
|
String pw = authPanel.getPassword();
|
|
vncContainer.remove(authPanel);
|
|
|
|
return pw;
|
|
}
|
|
*/
|
|
//
|
|
// Do the rest of the protocol initialisation.
|
|
//
|
|
|
|
void doProtocolInitialisation() throws IOException {
|
|
rfb.writeClientInit();
|
|
rfb.readServerInit();
|
|
/*
|
|
* Logger.log(Logger.INFO, "Desktop name is " + rfb.desktopName);
|
|
* Logger.log(Logger.INFO, "Desktop size is " + rfb.framebufferWidth +
|
|
* " x " + rfb.framebufferHeight);
|
|
*/
|
|
setEncodings();
|
|
|
|
showConnectionStatus(null);
|
|
}
|
|
|
|
//
|
|
// Send current encoding list to the RFB server.
|
|
//
|
|
|
|
int[] encodingsSaved;
|
|
int nEncodingsSaved;
|
|
|
|
void setEncodings() {
|
|
setEncodings(false);
|
|
}
|
|
|
|
void autoSelectEncodings() {
|
|
setEncodings(true);
|
|
}
|
|
|
|
void setEncodings(boolean autoSelectOnly) {
|
|
if (options == null || rfb == null || !rfb.inNormalProtocol)
|
|
return;
|
|
|
|
int preferredEncoding = options.preferredEncoding;
|
|
if (preferredEncoding == -1) {
|
|
long kbitsPerSecond = rfb.kbitsPerSecond();
|
|
if (nEncodingsSaved < 1) {
|
|
// Choose Tight or ZRLE encoding for the very first update.
|
|
// Logger.log(Logger.INFO, "Using Tight/ZRLE encodings");
|
|
preferredEncoding = RfbProto.EncodingTight;
|
|
} else if (kbitsPerSecond > 2000
|
|
&& encodingsSaved[0] != RfbProto.EncodingHextile) {
|
|
// Switch to Hextile if the connection speed is above 2Mbps.
|
|
Logger.log(Logger.INFO, "Throughput " + kbitsPerSecond
|
|
+ " kbit/s - changing to Hextile encoding");
|
|
preferredEncoding = RfbProto.EncodingHextile;
|
|
} else if (kbitsPerSecond < 1000
|
|
&& encodingsSaved[0] != RfbProto.EncodingTight) {
|
|
// Switch to Tight/ZRLE if the connection speed is below 1Mbps.
|
|
Logger.log(Logger.INFO, "Throughput " + kbitsPerSecond
|
|
+ " kbit/s - changing to Tight/ZRLE encodings");
|
|
preferredEncoding = RfbProto.EncodingTight;
|
|
} else {
|
|
// Don't change the encoder.
|
|
if (autoSelectOnly)
|
|
return;
|
|
preferredEncoding = encodingsSaved[0];
|
|
}
|
|
} else {
|
|
// Auto encoder selection is not enabled.
|
|
if (autoSelectOnly)
|
|
return;
|
|
}
|
|
|
|
int[] encodings = new int[20];
|
|
int nEncodings = 0;
|
|
|
|
encodings[nEncodings++] = preferredEncoding;
|
|
if (options.useCopyRect) {
|
|
encodings[nEncodings++] = RfbProto.EncodingCopyRect;
|
|
}
|
|
|
|
if (preferredEncoding != RfbProto.EncodingTight) {
|
|
encodings[nEncodings++] = RfbProto.EncodingTight;
|
|
}
|
|
if (preferredEncoding != RfbProto.EncodingZRLE) {
|
|
encodings[nEncodings++] = RfbProto.EncodingZRLE;
|
|
}
|
|
if (preferredEncoding != RfbProto.EncodingHextile) {
|
|
encodings[nEncodings++] = RfbProto.EncodingHextile;
|
|
}
|
|
if (preferredEncoding != RfbProto.EncodingZlib) {
|
|
encodings[nEncodings++] = RfbProto.EncodingZlib;
|
|
}
|
|
if (preferredEncoding != RfbProto.EncodingCoRRE) {
|
|
encodings[nEncodings++] = RfbProto.EncodingCoRRE;
|
|
}
|
|
if (preferredEncoding != RfbProto.EncodingRRE) {
|
|
encodings[nEncodings++] = RfbProto.EncodingRRE;
|
|
}
|
|
|
|
if (options.compressLevel >= 0 && options.compressLevel <= 9) {
|
|
encodings[nEncodings++] = RfbProto.EncodingCompressLevel0
|
|
+ options.compressLevel;
|
|
}
|
|
if (options.jpegQuality >= 0 && options.jpegQuality <= 9) {
|
|
encodings[nEncodings++] = RfbProto.EncodingQualityLevel0
|
|
+ options.jpegQuality;
|
|
}
|
|
|
|
if (options.requestCursorUpdates) {
|
|
encodings[nEncodings++] = RfbProto.EncodingXCursor;
|
|
encodings[nEncodings++] = RfbProto.EncodingRichCursor;
|
|
if (!options.ignoreCursorUpdates)
|
|
encodings[nEncodings++] = RfbProto.EncodingPointerPos;
|
|
}
|
|
|
|
encodings[nEncodings++] = RfbProto.EncodingLastRect;
|
|
encodings[nEncodings++] = RfbProto.EncodingNewFBSize;
|
|
|
|
boolean encodingsWereChanged = false;
|
|
if (nEncodings != nEncodingsSaved) {
|
|
encodingsWereChanged = true;
|
|
} else {
|
|
for (int i = 0; i < nEncodings; i++) {
|
|
if (encodings[i] != encodingsSaved[i]) {
|
|
encodingsWereChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (encodingsWereChanged) {
|
|
try {
|
|
rfb.writeSetEncodings(encodings, nEncodings);
|
|
if (vc != null) {
|
|
vc.softCursorFree();
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.log(Logger.ERROR, e.toString(), e);
|
|
}
|
|
encodingsSaved = encodings;
|
|
nEncodingsSaved = nEncodings;
|
|
}
|
|
}
|
|
|
|
//
|
|
// setCutText() - send the given cut text to the RFB server.
|
|
//
|
|
|
|
void setCutText(String text) {
|
|
try {
|
|
if (rfb != null && rfb.inNormalProtocol) {
|
|
rfb.writeClientCutText(text);
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.log(Logger.ERROR, e.toString(), e);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Order change in session recording status. To stop recording, pass
|
|
// null in place of the fname argument.
|
|
//
|
|
|
|
void setRecordingStatus(String fname) {
|
|
synchronized (recordingSync) {
|
|
sessionFileName = fname;
|
|
recordingStatusChanged = true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Start or stop session recording. Returns true if this method call
|
|
// causes recording of a new session.
|
|
//
|
|
|
|
boolean checkRecordingStatus() throws IOException {
|
|
synchronized (recordingSync) {
|
|
if (recordingStatusChanged) {
|
|
recordingStatusChanged = false;
|
|
if (sessionFileName != null) {
|
|
startRecording();
|
|
return true;
|
|
} else {
|
|
stopRecording();
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Start session recording.
|
|
//
|
|
|
|
protected void startRecording() throws IOException {
|
|
/*
|
|
* synchronized(recordingSync) {
|
|
*
|
|
* if (!recordingActive) { // Save settings to restore them after
|
|
* recording the session. cursorUpdatesDef =
|
|
* options.choices[options.cursorUpdatesIndex].getSelectedItem();
|
|
* eightBitColorsDef =
|
|
* options.choices[options.eightBitColorsIndex].getSelectedItem(); //
|
|
* Set options to values suitable for recording.
|
|
* options.choices[options.cursorUpdatesIndex].select("Disable");
|
|
* options.choices[options.cursorUpdatesIndex].setEnabled(false);
|
|
* options.setEncodings();
|
|
* options.choices[options.eightBitColorsIndex].select("No");
|
|
* options.choices[options.eightBitColorsIndex].setEnabled(false);
|
|
* options.setColorFormat(); } else { rfb.closeSession(); }
|
|
*
|
|
* Logger.log(Logger.INFO, "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(); Logger.log(Logger.INFO,
|
|
* "Session recording stopped."); } sessionFileName = null;
|
|
* recordingActive = false; }
|
|
*/
|
|
}
|
|
|
|
//
|
|
// readParameters() - read parameters from the html source or from the
|
|
// command line. On the command line, the arguments are just a sequence of
|
|
// param_name/param_value pairs where the names and values correspond to
|
|
// those expected in the html applet tag source.
|
|
//
|
|
|
|
void readParameters() {
|
|
String str;
|
|
host = readParameter("HOST", true);
|
|
|
|
str = readParameter("PORT", true);
|
|
port = Integer.parseInt(str);
|
|
|
|
proxyHost = readParameter("PROXYHOST", false);
|
|
if (proxyHost != null) {
|
|
str = readParameter("PROXYPORT", true);
|
|
proxyPort = Integer.parseInt(str);
|
|
}
|
|
|
|
// Read "ENCPASSWORD" or "PASSWORD" parameter if specified.
|
|
readPasswordParameters();
|
|
|
|
if (inAnApplet) {
|
|
str = readParameter("Open New Window", false);
|
|
if (str != null && str.equalsIgnoreCase("Yes"))
|
|
inSeparateFrame = true;
|
|
}
|
|
|
|
// "Show Controls" set to "No" disables button panel.
|
|
showControls = false;
|
|
str = readParameter("Show Controls", false);
|
|
if (str != null && str.equalsIgnoreCase("No"))
|
|
showControls = false;
|
|
|
|
// "Offer Relogin" set to "No" disables "Login again" and "Close
|
|
// window" buttons under error messages in applet mode.
|
|
offerRelogin = false;
|
|
str = readParameter("Offer Relogin", false);
|
|
if (str != null && str.equalsIgnoreCase("No"))
|
|
offerRelogin = false;
|
|
|
|
// Do we continue showing desktop on remote disconnect?
|
|
showOfflineDesktop = false;
|
|
str = readParameter("Show Offline Desktop", false);
|
|
if (str != null && str.equalsIgnoreCase("Yes"))
|
|
showOfflineDesktop = true;
|
|
|
|
// Fine tuning options.
|
|
deferScreenUpdates = readIntParameter("Defer screen updates", 20);
|
|
deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
|
|
deferUpdateRequests = readIntParameter("Defer update requests", 50);
|
|
|
|
// SocketFactory.
|
|
socketFactory = readParameter("SocketFactory", false);
|
|
}
|
|
|
|
//
|
|
// Read password parameters.
|
|
//
|
|
|
|
private void readPasswordParameters() {
|
|
String str = readParameter("SID", false);
|
|
if (str != null)
|
|
passwordParam = str;
|
|
}
|
|
|
|
public String readParameter(String name, boolean required) {
|
|
if (inAnApplet) {
|
|
String s = applet.getParameter(name);
|
|
// Logger.log(Logger.INFO, "getParameter " + name + " = " + s);
|
|
if ((s == null) && required) {
|
|
fatalError(name + " parameter not specified");
|
|
}
|
|
return s;
|
|
}
|
|
|
|
for (int i = 0; i < mainArgs.length; i += 2) {
|
|
if (mainArgs[i].equalsIgnoreCase(name)) {
|
|
try {
|
|
return mainArgs[i + 1];
|
|
} catch (Exception e) {
|
|
if (required) {
|
|
fatalError(name + " parameter not specified");
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
if (required) {
|
|
fatalError(name + " parameter not specified");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
int readIntParameter(String name, int defaultValue) {
|
|
String str = readParameter(name, false);
|
|
int result = defaultValue;
|
|
if (str != null) {
|
|
try {
|
|
result = Integer.parseInt(str);
|
|
} catch (NumberFormatException e) {
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//
|
|
// moveFocusToDesktop() - move keyboard focus either to VncCanvas.
|
|
//
|
|
|
|
void moveFocusToDesktop() {
|
|
if (vncContainer != null) {
|
|
if (vc != null && vncContainer.isAncestorOf(vc))
|
|
vc.requestFocus();
|
|
}
|
|
}
|
|
|
|
//
|
|
// disconnect() - close connection to server.
|
|
//
|
|
|
|
synchronized public void disconnect() {
|
|
Logger.log(Logger.INFO, "Disconnect");
|
|
|
|
synchronized (this) {
|
|
if (rfb != null)
|
|
rfb.close();
|
|
}
|
|
// options.dispose();
|
|
// clipboard.dispose();
|
|
if (rec != null)
|
|
rec.dispose();
|
|
|
|
if (inAnApplet) {
|
|
showMessage("Disconnected");
|
|
} else {
|
|
System.exit(0);
|
|
}
|
|
}
|
|
|
|
//
|
|
// fatalError() - print out a fatal error message.
|
|
// FIXME: Do we really need two versions of the fatalError() method?
|
|
//
|
|
|
|
synchronized public void fatalError(String str) {
|
|
Logger.log(Logger.INFO, str);
|
|
|
|
if (inAnApplet) {
|
|
// vncContainer null, applet not inited,
|
|
// can not present the error to the user.
|
|
Thread.currentThread().stop();
|
|
} else {
|
|
System.exit(1);
|
|
}
|
|
}
|
|
|
|
synchronized public void fatalError(String str, Exception e) {
|
|
/*
|
|
* if (rfb != null && rfb.closed()) { // Not necessary to show error
|
|
* message if the error was caused // by I/O problems after the
|
|
* rfb.close() method call. Logger.log(Logger.INFO,
|
|
* "RFB thread finished"); return; }
|
|
*/
|
|
if (str == null) {
|
|
str = "";
|
|
}
|
|
Logger.log(Logger.INFO, str, e);
|
|
showMessage(str);
|
|
}
|
|
|
|
//
|
|
// Show message text and optionally "Relogin" and "Close" buttons.
|
|
//
|
|
|
|
void showMessage(String msg) {
|
|
if (vc != null) {
|
|
vc.paintErrorString(msg);
|
|
return;
|
|
}
|
|
|
|
Logger.log(Logger.INFO, msg);
|
|
|
|
/*
|
|
* vncContainer.removeAll();
|
|
*
|
|
* Logger.log(Logger.INFO, "showMessage " + msg);
|
|
*
|
|
* Label errLabel = new Label(msg, Label.CENTER); errLabel.setFont(new
|
|
* Font("Helvetica", Font.PLAIN, 12));
|
|
*
|
|
* if (offerRelogin) {
|
|
*
|
|
* Panel gridPanel = new Panel(new GridLayout(0, 1)); Panel outerPanel =
|
|
* new Panel(new FlowLayout(FlowLayout.LEFT));
|
|
* outerPanel.add(gridPanel); vncContainer.setLayout(new
|
|
* FlowLayout(FlowLayout.LEFT, 30, 16)); vncContainer.add(outerPanel);
|
|
* Panel textPanel = new Panel(new FlowLayout(FlowLayout.CENTER));
|
|
* textPanel.add(errLabel); gridPanel.add(textPanel); gridPanel.add(new
|
|
* ReloginPanel(this));
|
|
*
|
|
* } else {
|
|
*
|
|
* vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30));
|
|
* vncContainer.add(errLabel);
|
|
*
|
|
* }
|
|
*
|
|
* if (inSeparateFrame) { vncFrame.pack(); } else { validate(); }
|
|
*/
|
|
}
|
|
|
|
//
|
|
// Stop the applet.
|
|
// Main applet thread will terminate on first exception
|
|
// after seeing that rfb has been closed.
|
|
//
|
|
|
|
public void stop() {
|
|
Logger.log(Logger.INFO, "Stopping applet");
|
|
synchronized (this) {
|
|
if (rfb != null) {
|
|
rfb.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// This method is called before the applet is destroyed.
|
|
//
|
|
|
|
public void destroy() {
|
|
Logger.log(Logger.INFO, "Destroying applet");
|
|
|
|
vncContainer.removeAll();
|
|
// options.dispose();
|
|
// clipboard.dispose();
|
|
if (rec != null)
|
|
rec.dispose();
|
|
synchronized (this) {
|
|
if (rfb != null)
|
|
rfb.close();
|
|
}
|
|
if (inSeparateFrame)
|
|
vncFrame.dispose();
|
|
}
|
|
|
|
//
|
|
// Start/stop receiving mouse events.
|
|
//
|
|
|
|
public void enableInput(boolean enable) {
|
|
vc.enableInput(enable);
|
|
}
|
|
|
|
//
|
|
// Close application properly on window close event.
|
|
//
|
|
|
|
public void windowClosing(WindowEvent evt) {
|
|
Logger.log(Logger.INFO, "Closing window");
|
|
if (rfb != null)
|
|
disconnect();
|
|
|
|
vncContainer.hide();
|
|
|
|
if (!inAnApplet) {
|
|
System.exit(0);
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return ("{ConsoleViewer-"
|
|
+ host
|
|
+ ":"
|
|
+ port
|
|
+ (" created=\"" + new Date(createTime)) + "\""
|
|
+ (" lastused=\"" + new Date(lastUsedTime)) + "\""
|
|
+ (clientStream != null ? (" client=" + clientStreamInfo) : "") + "}");
|
|
}
|
|
|
|
}
|