123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
- * Copyright 2009-2013 Pierre Ossman <ossman@cendio.se> for Cendio AB
- * Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved.
- * Copyright (C) 2011-2019 Brian P. Hinz
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
- //
- // CConn
- //
- // Methods on CConn are called from both the GUI thread and the thread which
- // processes incoming RFB messages ("the RFB thread"). This means we need to
- // be careful with synchronization here.
- //
- // Any access to writer() must not only be synchronized, but we must also make
- // sure that the connection is in RFBSTATE_NORMAL. We are guaranteed this for
- // any code called after serverInit() has been called. Since the DesktopWindow
- // isn't created until then, any methods called only from DesktopWindow can
- // assume that we are in RFBSTATE_NORMAL.
-
- package com.tigervnc.vncviewer;
-
- import java.awt.*;
- import java.awt.datatransfer.StringSelection;
- import java.awt.event.*;
- import java.awt.Toolkit;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.util.jar.Attributes;
- import java.util.jar.Manifest;
- import javax.swing.*;
- import javax.swing.ImageIcon;
- import java.net.InetSocketAddress;
- import java.net.SocketException;
- import java.util.*;
- import java.util.prefs.*;
-
- import com.tigervnc.rdr.*;
- import com.tigervnc.rfb.*;
- import com.tigervnc.rfb.Point;
- import com.tigervnc.rfb.Exception;
- import com.tigervnc.network.Socket;
- import com.tigervnc.network.TcpSocket;
-
- import static com.tigervnc.vncviewer.Parameters.*;
-
- public class CConn extends CConnection implements
- FdInStreamBlockCallback, ActionListener {
-
- // 8 colours (1 bit per component)
- static final PixelFormat verylowColorPF =
- new PixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0);
-
- // 64 colours (2 bits per component)
- static final PixelFormat lowColorPF =
- new PixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0);
-
- // 256 colours (2-3 bits per component)
- static final PixelFormat mediumColorPF =
- new PixelFormat(8, 8, false, true, 7, 7, 3, 5, 2, 0);
-
- ////////////////////////////////////////////////////////////////////
- // The following methods are all called from the RFB thread
-
- public CConn(String vncServerName, Socket socket)
- {
- serverHost = null; serverPort = 0; desktop = null;
- updateCount = 0; pixelCount = 0;
- lastServerEncoding = -1;
-
- setShared(shared.getValue());
- sock = socket;
-
- server.supportsLocalCursor = true;
- server.supportsDesktopResize = true;
- server.supportsClientRedirect = true;
-
- if (customCompressLevel.getValue())
- setCompressLevel(compressLevel.getValue());
-
- if (!noJpeg.getValue())
- setQualityLevel(qualityLevel.getValue());
-
- if (sock == null) {
- setServerName(Hostname.getHost(vncServerName));
- setServerPort(Hostname.getPort(vncServerName));
- try {
- if (tunnel.getValue() || !via.getValue().isEmpty()) {
- int localPort = TcpSocket.findFreeTcpPort();
- if (localPort == 0)
- throw new Exception("Could not obtain free TCP port");
- String gatewayHost = Tunnel.getSshHost();
- if (gatewayHost.isEmpty())
- gatewayHost = getServerName();
- Tunnel.createTunnel(gatewayHost, getServerName(),
- getServerPort(), localPort);
- sock = new TcpSocket("localhost", localPort);
- vlog.info("connected to localhost port "+localPort);
- } else {
- sock = new TcpSocket(getServerName(), getServerPort());
- vlog.info("connected to host "+getServerName()+" port "+getServerPort());
- }
- } catch (java.lang.Exception e) {
- throw new Exception(e.getMessage());
- }
- } else {
- String name = sock.getPeerEndpoint();
- if (listenMode.getValue())
- vlog.info("Accepted connection from " + name);
- else
- vlog.info("connected to host "+Hostname.getHost(name)+" port "+Hostname.getPort(name));
- }
-
- // See callback below
- sock.inStream().setBlockCallback(this);
-
- setStreams(sock.inStream(), sock.outStream());
-
- initialiseProtocol();
-
- OptionsDialog.addCallback("handleOptions", this);
- }
-
- public String connectionInfo() {
- String info = new String("Desktop name: %s%n"+
- "Host: %s:%d%n"+
- "Size: %dx%d%n"+
- "Pixel format: %s%n"+
- " (server default: %s)%n"+
- "Requested encoding: %s%n"+
- "Last used encoding: %s%n"+
- "Line speed estimate: %d kbit/s%n"+
- "Protocol version: %d.%d%n"+
- "Security method: %s [%s]%n");
- String infoText =
- String.format(info, server.name(),
- sock.getPeerName(), sock.getPeerPort(),
- server.width(), server.height(),
- server.pf().print(),
- serverPF.print(),
- Encodings.encodingName(getPreferredEncoding()),
- Encodings.encodingName(lastServerEncoding),
- sock.inStream().kbitsPerSecond(),
- server.majorVersion, server.minorVersion,
- Security.secTypeName(csecurity.getType()),
- csecurity.description());
-
- return infoText;
- }
-
- public int getUpdateCount()
- {
- return updateCount;
- }
-
- public int getPixelCount()
- {
- return pixelCount;
- }
-
- // The RFB core is not properly asynchronous, so it calls this callback
- // whenever it needs to block to wait for more data. Since FLTK is
- // monitoring the socket, we just make sure FLTK gets to run.
-
- public void blockCallback() {
- try {
- synchronized(this) {
- wait(1);
- }
- } catch (java.lang.InterruptedException e) {
- throw new Exception(e.getMessage());
- }
- }
-
- ////////////////////// CConnection callback methods //////////////////////
-
- // serverInit() is called when the serverInit message has been received. At
- // this point we create the desktop window and display it. We also tell the
- // server the pixel format and encodings to use and request the first update.
- public void initDone()
- {
- // If using AutoSelect with old servers, start in FullColor
- // mode. See comment in autoSelectFormatAndEncoding.
- if (server.beforeVersion(3, 8) && autoSelect.getValue())
- fullColor.setParam(true);
-
- serverPF = server.pf();
-
- desktop = new DesktopWindow(server.width(), server.height(),
- server.name(), serverPF, this);
- fullColorPF = desktop.getPreferredPF();
-
- // Force a switch to the format and encoding we'd like
- updatePixelFormat();
- int encNum = Encodings.encodingNum(preferredEncoding.getValue());
- if (encNum != -1)
- setPreferredEncoding(encNum);
- }
-
- // setDesktopSize() is called when the desktop size changes (including when
- // it is set initially).
- public void setDesktopSize(int w, int h)
- {
- super.setDesktopSize(w, h);
- resizeFramebuffer();
- }
-
- // setExtendedDesktopSize() is a more advanced version of setDesktopSize()
- public void setExtendedDesktopSize(int reason, int result,
- int w, int h, ScreenSet layout)
- {
- super.setExtendedDesktopSize(reason, result, w, h, layout);
-
- if ((reason == screenTypes.reasonClient) &&
- (result != screenTypes.resultSuccess)) {
- vlog.error("SetDesktopSize failed: " + result);
- return;
- }
-
- resizeFramebuffer();
- }
-
- // clientRedirect() migrates the client to another host/port
- public void clientRedirect(int port, String host, String x509subject)
- {
- try {
- sock.close();
- sock = new TcpSocket(host, port);
- vlog.info("Redirected to "+host+":"+port);
- setServerName(host);
- setServerPort(port);
- sock.inStream().setBlockCallback(this);
- setStreams(sock.inStream(), sock.outStream());
- if (desktop != null)
- desktop.dispose();
- initialiseProtocol();
- } catch (java.lang.Exception e) {
- throw new Exception(e.getMessage());
- }
- }
-
- // setName() is called when the desktop name changes
- public void setName(String name)
- {
- super.setName(name);
- if (desktop != null)
- desktop.setName(name);
- }
-
- // framebufferUpdateStart() is called at the beginning of an update.
- // Here we try to send out a new framebuffer update request so that the
- // next update can be sent out in parallel with us decoding the current
- // one.
- public void framebufferUpdateStart()
- {
-
- super.framebufferUpdateStart();
-
- }
-
- // framebufferUpdateEnd() is called at the end of an update.
- // For each rectangle, the FdInStream will have timed the speed
- // of the connection, allowing us to select format and encoding
- // appropriately, and then request another incremental update.
- public void framebufferUpdateEnd()
- {
- super.framebufferUpdateEnd();
-
- updateCount++;
-
- desktop.updateWindow();
-
- // Compute new settings based on updated bandwidth values
- if (autoSelect.getValue())
- autoSelectFormatAndEncoding();
- }
-
- // The rest of the callbacks are fairly self-explanatory...
-
- public void setColourMapEntries(int firstColor, int nColors, int[] rgbs)
- {
- vlog.error("Invalid SetColourMapEntries from server!");
- }
-
- public void bell()
- {
- if (acceptBell.getValue())
- desktop.getToolkit().beep();
- }
-
- public void serverCutText(String str, int len)
- {
- StringSelection buffer;
-
- if (!acceptClipboard.getValue())
- return;
-
- ClipboardDialog.serverCutText(str);
- }
-
- public void dataRect(Rect r, int encoding)
- {
- sock.inStream().startTiming();
-
- if (encoding != Encodings.encodingCopyRect)
- lastServerEncoding = encoding;
-
- super.dataRect(r, encoding);
-
- sock.inStream().stopTiming();
-
- pixelCount += r.area();
- }
-
- public void setCursor(int width, int height, Point hotspot,
- byte[] data)
- {
- desktop.setCursor(width, height, hotspot, data);
- }
-
- public void fence(int flags, int len, byte[] data)
- {
- // can't call super.super.fence(flags, len, data);
- server.supportsFence = true;
-
- if ((flags & fenceTypes.fenceFlagRequest) != 0) {
- // We handle everything synchronously so we trivially honor these modes
- flags = flags & (fenceTypes.fenceFlagBlockBefore | fenceTypes.fenceFlagBlockAfter);
-
- writer().writeFence(flags, len, data);
- return;
- }
- }
-
- ////////////////////// Internal methods //////////////////////
- public void resizeFramebuffer()
- {
- if (desktop == null)
- return;
-
- desktop.resizeFramebuffer(server.width(), server.height());
- }
-
- // autoSelectFormatAndEncoding() chooses the format and encoding appropriate
- // to the connection speed:
- //
- // First we wait for at least one second of bandwidth measurement.
- //
- // Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
- // which should be perceptually lossless.
- //
- // If the bandwidth is below that, we choose a more lossy JPEG quality.
- //
- // If the bandwidth drops below 256 Kbps, we switch to palette mode.
- //
- // Note: The system here is fairly arbitrary and should be replaced
- // with something more intelligent at the server end.
- //
- private void autoSelectFormatAndEncoding()
- {
- long kbitsPerSecond = sock.inStream().kbitsPerSecond();
- long timeWaited = sock.inStream().timeWaited();
- boolean newFullColour = fullColor.getValue();
- int newQualityLevel = qualityLevel.getValue();
-
- // Always use Tight
- setPreferredEncoding(Encodings.encodingTight);
-
- // Check that we have a decent bandwidth measurement
- if ((kbitsPerSecond == 0) || (timeWaited < 100))
- return;
-
- // Select appropriate quality level
- if (!noJpeg.getValue()) {
- if (kbitsPerSecond > 16000)
- newQualityLevel = 8;
- else
- newQualityLevel = 6;
-
- if (newQualityLevel != qualityLevel.getValue()) {
- vlog.info("Throughput "+kbitsPerSecond+
- " kbit/s - changing to quality "+newQualityLevel);
- qualityLevel.setParam(newQualityLevel);
- setQualityLevel(newQualityLevel);
- }
- }
-
- if (server.beforeVersion(3, 8)) {
- // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
- // cursors "asynchronously". If this happens in the middle of a
- // pixel format change, the server will encode the cursor with
- // the old format, but the client will try to decode it
- // according to the new format. This will lead to a
- // crash. Therefore, we do not allow automatic format change for
- // old servers.
- return;
- }
-
- // Select best color level
- newFullColour = (kbitsPerSecond > 256);
- if (newFullColour != fullColor.getValue()) {
- if (newFullColour)
- vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now enabled");
- else
- vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now disabled");
- fullColor.setParam(newFullColour);
- updatePixelFormat();
- }
- }
-
- // updatePixelFormat() requests an update from the server, having set the
- // format and encoding appropriately.
- private void updatePixelFormat()
- {
- PixelFormat pf;
-
- if (fullColor.getValue()) {
- pf = fullColorPF;
- } else {
- if (lowColorLevel.getValue() == 0) {
- pf = verylowColorPF;
- } else if (lowColorLevel.getValue() == 1) {
- pf = lowColorPF;
- } else {
- pf = mediumColorPF;
- }
- }
-
- String str = pf.print();
- vlog.info("Using pixel format " + str);
- setPF(pf);
- }
-
- public void handleOptions()
- {
-
- // Checking all the details of the current set of encodings is just
- // a pain. Assume something has changed, as resending the encoding
- // list is cheap. Avoid overriding what the auto logic has selected
- // though.
- if (!autoSelect.getValue()) {
- int encNum = Encodings.encodingNum(preferredEncoding.getValue());
-
- if (encNum != -1)
- this.setPreferredEncoding(encNum);
- }
-
- if (customCompressLevel.getValue())
- this.setCompressLevel(compressLevel.getValue());
- else
- this.setCompressLevel(-1);
-
- if (!noJpeg.getValue() && !autoSelect.getValue())
- this.setQualityLevel(qualityLevel.getValue());
- else
- this.setQualityLevel(-1);
-
- this.updatePixelFormat();
- }
-
- ////////////////////////////////////////////////////////////////////
- // The following methods are all called from the GUI thread
-
- // close() shuts down the socket, thus waking up the RFB thread.
- public void close() {
- if (closeListener != null) {
- JFrame f =
- (JFrame)SwingUtilities.getAncestorOfClass(JFrame.class, desktop);
- if (f != null)
- f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
- }
- shuttingDown = true;
- try {
- if (sock != null)
- sock.shutdown();
- } catch (java.lang.Exception e) {
- throw new Exception(e.getMessage());
- }
- }
-
- // writeClientCutText() is called from the clipboard dialog
- public void writeClientCutText(String str, int len) {
- if ((state() != stateEnum.RFBSTATE_NORMAL) || shuttingDown)
- return;
- writer().writeClientCutText(str, len);
- }
-
- public void actionPerformed(ActionEvent e) {}
-
- public Socket getSocket() {
- return sock;
- }
-
- ////////////////////////////////////////////////////////////////////
- // The following methods are called from both RFB and GUI threads
-
- // the following never change so need no synchronization:
-
- // access to desktop by different threads is specified in DesktopWindow
-
- // the following need no synchronization:
-
- // shuttingDown is set by the GUI thread and only ever tested by the RFB
- // thread after the window has been destroyed.
- boolean shuttingDown = false;
-
- // reading and writing int and boolean is atomic in java, so no
- // synchronization of the following flags is needed:
-
-
- // All menu, options, about and info stuff is done in the GUI thread (apart
- // from when constructed).
-
- // the following are only ever accessed by the GUI thread:
- private String serverHost;
- private int serverPort;
- private Socket sock;
-
- protected DesktopWindow desktop;
-
- private int updateCount;
- private int pixelCount;
-
- private PixelFormat serverPF;
- private PixelFormat fullColorPF;
-
- private int lastServerEncoding;
-
- public ActionListener closeListener = null;
-
- static LogWriter vlog = new LogWriter("CConn");
- }
|