All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tigervnc.vncviewer.VncViewer Maven / Gradle / Ivy

The newest version!
/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright 2011 Pierre Ossman  for Cendio AB
 * Copyright (C) 2011-2013 D. R. Commander.  All Rights Reserved.
 * Copyright (C) 2011-2015 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.
 */

//
// VncViewer - the VNC viewer applet.  It can also be run from the
// command-line, when it behaves as much as possibly like the windows and unix
// viewers.
//
// Unfortunately, because of the way Java classes are loaded on demand, only
// configuration parameters defined in this file can be set from the command
// line or in applet parameters.

package com.tigervnc.vncviewer;

import java.awt.*;
import java.awt.event.*;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import java.lang.Character;
import java.lang.reflect.*;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.*;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import javax.swing.UIManager.*;

import com.tigervnc.rdr.*;
import com.tigervnc.rfb.*;
import com.tigervnc.network.*;

public class VncViewer extends javax.swing.JApplet 
  implements Runnable, ActionListener {

  public static final String aboutText = new String("TigerVNC Java Viewer v%s (%s)%n"+
                                                    "Built on %s at %s%n"+
                                                    "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+
                                                    "See http://www.tigervnc.org for information on TigerVNC.");

  public static String version = null;
  public static String build = null;
  public static String buildDate = null;
  public static String buildTime = null;
  static ImageIcon frameIconSrc =
    new ImageIcon(VncViewer.class.getResource("tigervnc.ico"));
  public static final Image frameIcon = frameIconSrc.getImage();
  public static final ImageIcon logoIcon =
    new ImageIcon(VncViewer.class.getResource("tigervnc.png"));
  public static final Image logoImage = logoIcon.getImage();
  public static final InputStream timestamp =
    VncViewer.class.getResourceAsStream("timestamp");
  public static final String os = 
    System.getProperty("os.name").toLowerCase();

  public static void setLookAndFeel() {
    try {
      if (os.startsWith("mac os x")) {
        Class appClass = Class.forName("com.apple.eawt.Application");
        Method getApplication = 
          appClass.getMethod("getApplication", (Class[])null);
        Object app = getApplication.invoke(appClass);
        Class paramTypes[] = new Class[1];
        paramTypes[0] = Image.class;
        Method setDockIconImage = 
          appClass.getMethod("setDockIconImage", paramTypes);
        setDockIconImage.invoke(app, VncViewer.logoImage);
      }
      // Use Nimbus LookAndFeel if it's available, otherwise fallback
      // to the native laf, or Metal if no native laf is available.
      String laf = System.getProperty("swing.defaultlaf");
      if (laf == null) {
        LookAndFeelInfo[] installedLafs = UIManager.getInstalledLookAndFeels();
        for (int i = 0; i < installedLafs.length; i++) {
          if (installedLafs[i].getName().equals("Nimbus"))
            laf = installedLafs[i].getClassName();
        }
        if (laf == null)
          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
      }
      UIManager.setLookAndFeel(laf);
      if (UIManager.getLookAndFeel().getName().equals("Metal")) {
        UIManager.put("swing.boldMetal", Boolean.FALSE);
        Enumeration keys = UIManager.getDefaults().keys();
        while (keys.hasMoreElements()) {
          Object key = keys.nextElement();
          Object value = UIManager.get(key);
          if (value instanceof FontUIResource) {
            String name = ((FontUIResource)value).getName();
            int style = ((FontUIResource)value).getStyle();
            int size = ((FontUIResource)value).getSize()-1;
            FontUIResource f = new FontUIResource(name, style, size);
            UIManager.put(key, f);
          }
        }
      } else if (UIManager.getLookAndFeel().getName().equals("Nimbus")) {
        Font f = UIManager.getFont("TitledBorder.font");
        String name = f.getName();
        int style = f.getStyle();
        int size = f.getSize()-2;
        FontUIResource r = new FontUIResource(name, style, size);
      	UIManager.put("TitledBorder.font", r);
      }
    } catch (java.lang.Exception e) {
      vlog.info(e.toString());
    }
  }

  public static void main(String[] argv) {
    setLookAndFeel();
    VncViewer viewer = new VncViewer(argv);
    viewer.start();
  }


  public VncViewer(String[] argv) {
    embed.setParam(false);

    // load user preferences
    UserPreferences.load("global");

    SecurityClient.setDefaults();

    // Write about text to console, still using normal locale codeset
    getTimestamp();
    System.err.format("%n");
    System.err.format(aboutText, version, build, buildDate, buildTime);
    System.err.format("%n");

    Configuration.enableViewerParams();

    // Override defaults with command-line options
    for (int i = 0; i < argv.length; i++) {
      if (argv[i].length() == 0)
        continue;

      if (argv[i].equalsIgnoreCase("-config")) {
        if (++i >= argv.length) usage();
          Configuration.load(argv[i]);
        continue;
      }

      if (argv[i].equalsIgnoreCase("-log")) {
        if (++i >= argv.length) usage();
        System.err.println("Log setting: "+argv[i]);
        LogWriter.setLogParams(argv[i]);
        continue;
      }

      if (Configuration.setParam(argv[i]))
        continue;

      if (argv[i].charAt(0) == '-') {
        if (i+1 < argv.length) {
          if (Configuration.setParam(argv[i].substring(1), argv[i+1])) {
            i++;
            continue;
          }
        }
        usage();
      }

      if (vncServerName.getValue() != null)
        usage();
      vncServerName.setParam(argv[i]);
    }

    if (!autoSelect.hasBeenSet()) {
      // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used
      autoSelect.setParam(!preferredEncoding.hasBeenSet() &&
                          !fullColour.hasBeenSet() &&
                          !fullColourAlias.hasBeenSet());
    }
    if (!fullColour.hasBeenSet() && !fullColourAlias.hasBeenSet()) {
      // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set
      if (!autoSelect.getValue() && (lowColourLevel.hasBeenSet() ||
                          lowColourLevelAlias.hasBeenSet())) {
        fullColour.setParam(false);
      }
    }
    if (!customCompressLevel.hasBeenSet()) {
      // Default to CustomCompressLevel=1 if CompressLevel is used.
      customCompressLevel.setParam(compressLevel.hasBeenSet());
    }
  }

  public static void usage() {
    String usage = ("\nusage: vncviewer [options/parameters] "+
                    "[host:displayNum]\n"+
                    "       vncviewer [options/parameters] -listen [port] "+
                    "[options/parameters]\n"+
                    "\n"+
                    "Options:\n"+
                    "  -log     configure logging level\n"+
                    "\n"+
                    "Parameters can be turned on with - or off with "+
                    "-=0\n"+
                    "Parameters which take a value can be specified as "+
                    "- \n"+
                    "Other valid forms are = -= "+
                    "--=\n"+
                    "Parameter names are case-insensitive.  The parameters "+
                    "are:\n"+
                    "\n");
    System.err.print(usage);

    Configuration.listParams(79, 14);
    String propertiesString = ("\n"+
"System Properties (adapted from the TurboVNC vncviewer man page)\n"+
"  When started with the -via option, vncviewer reads the VNC_VIA_CMD\n"+
"  System property, expands patterns beginning with the \"%\" character,\n"+
"  and uses the resulting command line to establish the secure tunnel\n"+
"  to the VNC gateway.  If VNC_VIA_CMD is not set, this command line\n"+
"  defaults to \"/usr/bin/ssh -f -L %L:%H:%R %G sleep 20\".\n"+
"\n"+
"  The following patterns are recognized in the VNC_VIA_CMD property\n"+
"  (note that all of the patterns %G, %H, %L and %R must be present in \n"+
"  the command template):\n"+
"\n"+
"  \t%%     A literal \"%\";\n"+
"\n"+
"  \t%G     gateway machine name;\n"+
"\n"+
"  \t%H     remote VNC machine name, (as known to the gateway);\n"+
"\n"+
"  \t%L     local TCP port number;\n"+
"\n"+
"  \t%R     remote TCP port number.\n"+
"\n"+
"  When started with the -tunnel option, vncviewer reads the VNC_TUNNEL_CMD\n"+
"  System property, expands patterns beginning with the \"%\" character, and\n"+
"  uses the resulting command line to establish the secure tunnel to the\n"+
"  VNC server.  If VNC_TUNNEL_CMD is not set, this command line defaults\n"+
"  to \"/usr/bin/ssh -f -L %L:localhost:%R %H sleep 20\".\n"+
"\n"+
"  The following patterns are recognized in the VNC_TUNNEL_CMD property\n"+
"  (note that all of the patterns %H, %L and %R must be present in \n"+
"  the command template):\n"+
"\n"+
"  \t%%     A literal \"%\";\n"+
"\n"+
"  \t%H     remote VNC machine name (as known to the client);\n"+
"\n"+
"  \t%L     local TCP port number;\n"+
"\n"+
"  \t%R     remote TCP port number.\n"+
"\n");
    System.err.print(propertiesString);
    // Technically, we shouldn't use System.exit here but if there is a parameter
    // error then the problem is in the index/html file anyway.
    System.exit(1);
  }

  public VncViewer() {
    UserPreferences.load("global");
    embed.setParam(true);
  }

  public static void newViewer(VncViewer oldViewer, Socket sock, boolean close) {
    VncViewer viewer = new VncViewer();
    viewer.embed.setParam(oldViewer.embed.getValue());
    viewer.sock = sock;
    viewer.start();
    if (close)
      oldViewer.exit(0);
  }

  public static void newViewer(VncViewer oldViewer, Socket sock) {
    newViewer(oldViewer, sock, false);
  }

  public static void newViewer(VncViewer oldViewer) {
    newViewer(oldViewer, null);
  }

  public boolean isAppletDragStart(MouseEvent e) {
    if(e.getID() == MouseEvent.MOUSE_DRAGGED) {
      // Drag undocking on Mac works, but introduces a host of
      // problems so disable it for now.
      if (os.startsWith("mac os x"))
        return false;
      else if (os.startsWith("windows"))
        return (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0;
      else
        return (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0;
    } else {
      return false;
    }
  }

  public void appletDragStarted() {
    embed.setParam(false);
    cc.recreateViewport();
    JFrame f = (JFrame)JOptionPane.getFrameForComponent(this);
    // The default JFrame created by the drag event will be
    // visible briefly between appletDragStarted and Finished.
    if (f != null)
      f.setSize(0, 0);
  }

  public void appletDragFinished() {
    cc.setEmbeddedFeatures(true);
    JFrame f = (JFrame)JOptionPane.getFrameForComponent(this);
    if (f != null)
      f.dispose();
  }

  public void setAppletCloseListener(ActionListener cl) {
    cc.setCloseListener(cl);
  }

  public void appletRestored() {
    cc.setEmbeddedFeatures(false);
    cc.setCloseListener(null);
  }

  public void init() {
    vlog.debug("init called");
    Container parent = getParent();
    while (!parent.isFocusCycleRoot()) {
      parent = parent.getParent();
    }
    ((Frame)parent).setModalExclusionType(null);
    parent.setFocusable(false);
    parent.setFocusTraversalKeysEnabled(false);
    setLookAndFeel();
    setBackground(Color.white);
  }

  private void getTimestamp() {
    if (version == null || build == null) {
      try {
        Manifest manifest = new Manifest(timestamp);
        Attributes attributes = manifest.getMainAttributes();
        version = attributes.getValue("Version");
        build = attributes.getValue("Build");
        buildDate = attributes.getValue("Package-Date");
        buildTime = attributes.getValue("Package-Time");
      } catch (java.lang.Exception e) { }
    }
  }

  public void start() {
    vlog.debug("start called");
    getTimestamp();
    if (embed.getValue() && nViewers == 0) {
      alwaysShowServerDialog.setParam(false);
      Configuration.global().readAppletParams(this);
      fullScreen.setParam(false);
      scalingFactor.setParam("100");
      String host = getCodeBase().getHost();
      if (vncServerName.getValue() == null && vncServerPort.getValue() != 0) {
        int port = vncServerPort.getValue();
        vncServerName.setParam(host + ((port >= 5900 && port <= 5999)
                                       ? (":"+(port-5900))
                                       : ("::"+port)));
      }
    }
    nViewers++;
    thread = new Thread(this);
    thread.start();
  }

  public void exit(int n) {
    nViewers--;
    if (nViewers > 0)
      return;
    if (embed.getValue())
      destroy();
    else
      System.exit(n);
  }

  // If "Reconnect" button is pressed
  public void actionPerformed(ActionEvent e) {
    getContentPane().removeAll();
    start();
  }

  void reportException(java.lang.Exception e) {
    String title, msg = e.getMessage();
    int msgType = JOptionPane.ERROR_MESSAGE;
    title = "TigerVNC Viewer : Error";
    e.printStackTrace();
    if (embed.getValue()) {
      getContentPane().removeAll();
      JLabel label = new JLabel("
" + title + "

" + msg + "

", JLabel.CENTER); label.setFont(new Font("Helvetica", Font.PLAIN, 24)); label.setMaximumSize(new Dimension(getSize().width, 100)); label.setVerticalAlignment(JLabel.CENTER); label.setAlignmentX(Component.CENTER_ALIGNMENT); JButton button = new JButton("Reconnect"); button.addActionListener(this); button.setMaximumSize(new Dimension(200, 30)); button.setAlignmentX(Component.CENTER_ALIGNMENT); setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); add(label); add(button); validate(); repaint(); } else { JOptionPane.showMessageDialog(null, msg, title, msgType); } } CConn cc; public void run() { cc = null; if (listenMode.getValue()) { int port = 5500; if (vncServerName.getValue() != null && Character.isDigit(vncServerName.getValue().charAt(0))) port = Integer.parseInt(vncServerName.getValue()); TcpListener listener = null; try { listener = new TcpListener(null, port); } catch (java.lang.Exception e) { reportException(e); exit(1); } vlog.info("Listening on port "+port); while (true) { Socket new_sock = listener.accept(); if (new_sock != null) newViewer(this, new_sock, true); } } try { cc = new CConn(this, sock, vncServerName.getValue()); while (!cc.shuttingDown) cc.processMsg(); exit(0); } catch (java.lang.Exception e) { if (cc == null || !cc.shuttingDown) { reportException(e); if (cc != null) cc.deleteWindow(); } else if (embed.getValue()) { reportException(new java.lang.Exception("Connection closed")); exit(0); } exit(1); } } static BoolParameter noLionFS = new BoolParameter("NoLionFS", "On Mac systems, setting this parameter will force the use of the old "+ "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+ "Lion or later.", false); BoolParameter embed = new BoolParameter("Embed", "If the viewer is being run as an applet, display its output to " + "an embedded frame in the browser window rather than to a dedicated " + "window. Embed=1 implies FullScreen=0 and Scale=100.", false); BoolParameter useLocalCursor = new BoolParameter("UseLocalCursor", "Render the mouse cursor locally", true); BoolParameter sendLocalUsername = new BoolParameter("SendLocalUsername", "Send the local username for SecurityTypes "+ "such as Plain rather than prompting", true); StringParameter passwordFile = new StringParameter("PasswordFile", "Password file for VNC authentication", ""); AliasParameter passwd = new AliasParameter("passwd", "Alias for PasswordFile", passwordFile); BoolParameter autoSelect = new BoolParameter("AutoSelect", "Auto select pixel format and encoding", true); BoolParameter fullColour = new BoolParameter("FullColour", "Use full colour - otherwise 6-bit colour is "+ "used until AutoSelect decides the link is "+ "fast enough", true); AliasParameter fullColourAlias = new AliasParameter("FullColor", "Alias for FullColour", fullColour); IntParameter lowColourLevel = new IntParameter("LowColorLevel", "Color level to use on slow connections. "+ "0 = Very Low (8 colors), 1 = Low (64 colors), "+ "2 = Medium (256 colors)", 2); AliasParameter lowColourLevelAlias = new AliasParameter("LowColourLevel", "Alias for LowColorLevel", lowColourLevel); StringParameter preferredEncoding = new StringParameter("PreferredEncoding", "Preferred encoding to use (Tight, ZRLE, "+ "hextile or raw) - implies AutoSelect=0", "Tight"); BoolParameter viewOnly = new BoolParameter("ViewOnly", "Don't send any mouse or keyboard events to "+ "the server", false); BoolParameter shared = new BoolParameter("Shared", "Don't disconnect other viewers upon "+ "connection - share the desktop instead", false); BoolParameter fullScreen = new BoolParameter("FullScreen", "Full Screen Mode", false); BoolParameter fullScreenAllMonitors = new BoolParameter("FullScreenAllMonitors", "Enable full screen over all monitors", true); BoolParameter acceptClipboard = new BoolParameter("AcceptClipboard", "Accept clipboard changes from the server", true); BoolParameter sendClipboard = new BoolParameter("SendClipboard", "Send clipboard changes to the server", true); static IntParameter maxCutText = new IntParameter("MaxCutText", "Maximum permitted length of an outgoing clipboard update", 262144); StringParameter menuKey = new StringParameter("MenuKey", "The key which brings up the popup menu", "F8"); StringParameter desktopSize = new StringParameter("DesktopSize", "Reconfigure desktop size on the server on "+ "connect (if possible)", ""); BoolParameter listenMode = new BoolParameter("listen", "Listen for connections from VNC servers", false); StringParameter scalingFactor = new StringParameter("ScalingFactor", "Reduce or enlarge the remote desktop image. "+ "The value is interpreted as a scaling factor "+ "in percent. If the parameter is set to "+ "\"Auto\", then automatic scaling is "+ "performed. Auto-scaling tries to choose a "+ "scaling factor in such a way that the whole "+ "remote desktop will fit on the local screen. "+ "If the parameter is set to \"FixedRatio\", "+ "then automatic scaling is performed, but the "+ "original aspect ratio is preserved.", "100"); BoolParameter alwaysShowServerDialog = new BoolParameter("AlwaysShowServerDialog", "Always show the server dialog even if a server "+ "has been specified in an applet parameter or on "+ "the command line", false); StringParameter vncServerName = new StringParameter("Server", "The VNC server [:] or "+ "::", null); IntParameter vncServerPort = new IntParameter("Port", "The VNC server's port number, assuming it is on "+ "the host from which the applet was downloaded", 0); BoolParameter acceptBell = new BoolParameter("AcceptBell", "Produce a system beep when requested to by the server.", true); StringParameter via = new StringParameter("Via", "Automatically create an encrypted TCP tunnel to "+ "the gateway machine, then connect to the VNC host "+ "through that tunnel. By default, this option invokes "+ "SSH local port forwarding using the embedded JSch "+ "client, however an external SSH client may be specified "+ "using the \"-extSSH\" parameter. Note that when using "+ "the -via option, the VNC host machine name should be "+ "specified from the point of view of the gateway machine, "+ "e.g. \"localhost\" denotes the gateway, "+ "not the machine on which the viewer was launched. "+ "See the System Properties section below for "+ "information on configuring the -Via option.", null); BoolParameter tunnel = new BoolParameter("Tunnel", "The -Tunnel command is basically a shorthand for the "+ "-via command when the VNC server and SSH gateway are "+ "one and the same. -Tunnel creates an SSH connection "+ "to the server and forwards the VNC through the tunnel "+ "without the need to specify anything else.", false); BoolParameter extSSH = new BoolParameter("extSSH", "By default, SSH tunneling uses the embedded JSch client "+ "for tunnel creation. This option causes the client to "+ "invoke an external SSH client application for all tunneling "+ "operations. By default, \"/usr/bin/ssh\" is used, however "+ "the path to the external application may be specified using "+ "the -SSHClient option.", false); StringParameter extSSHClient = new StringParameter("extSSHClient", "Specifies the path to an external SSH client application "+ "that is to be used for tunneling operations when the -extSSH "+ "option is in effect.", "/usr/bin/ssh"); StringParameter extSSHArgs = new StringParameter("extSSHArgs", "Specifies the arguments string or command template to be used "+ "by the external SSH client application when the -extSSH option "+ "is in effect. The string will be processed according to the same "+ "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+ "system properties, and can be used to override those in a more "+ "command-line friendly way. If not specified, then the appropriate "+ "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", null); StringParameter sshConfig = new StringParameter("SSHConfig", "Specifies the path to an OpenSSH configuration file that to "+ "be parsed by the embedded JSch SSH client during tunneling "+ "operations.", FileUtils.getHomeDir()+".ssh/config"); StringParameter sshKey = new StringParameter("SSHKey", "When using the Via or Tunnel options with the embedded SSH client, "+ "this parameter specifies the text of the SSH private key to use when "+ "authenticating with the SSH server. You can use \\n within the string "+ "to specify a new line.", null); StringParameter sshKeyFile = new StringParameter("SSHKeyFile", "When using the Via or Tunnel options with the embedded SSH client, "+ "this parameter specifies a file that contains an SSH private key "+ "(or keys) to use when authenticating with the SSH server. If not "+ "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+ "Otherwise, the client will fallback to prompting for an SSH password.", null); StringParameter sshKeyPass = new StringParameter("SSHKeyPass", "When using the Via or Tunnel options with the embedded SSH client, "+ "this parameter specifies the passphrase for the SSH key.", null); BoolParameter customCompressLevel = new BoolParameter("CustomCompressLevel", "Use custom compression level. "+ "Default if CompressLevel is specified.", false); IntParameter compressLevel = new IntParameter("CompressLevel", "Use specified compression level "+ "0 = Low, 6 = High", 1); BoolParameter noJpeg = new BoolParameter("NoJPEG", "Disable lossy JPEG compression in Tight encoding.", false); IntParameter qualityLevel = new IntParameter("QualityLevel", "JPEG quality level. "+ "0 = Low, 9 = High", 8); StringParameter x509ca = new StringParameter("X509CA", "Path to CA certificate to use when authenticating remote servers "+ "using any of the X509 security schemes (X509None, X509Vnc, etc.). "+ "Must be in PEM format.", FileUtils.getHomeDir()+".vnc/x509_ca.pem"); StringParameter x509crl = new StringParameter("X509CRL", "Path to certificate revocation list to use in conjunction with "+ "-X509CA. Must also be in PEM format.", FileUtils.getHomeDir()+".vnc/x509_crl.pem"); StringParameter config = new StringParameter("config", "Specifies a configuration file to load.", null); Thread thread; Socket sock; static int nViewers; static LogWriter vlog = new LogWriter("VncViewer"); }