/* 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-2016 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.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.File; import java.lang.Character; import java.lang.reflect.*; import java.net.URL; import java.nio.CharBuffer; import java.util.*; import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.swing.*; import javax.swing.border.*; import javax.swing.plaf.FontUIResource; import javax.swing.SwingUtilities; import javax.swing.UIManager.*; import com.tigervnc.rdr.*; import com.tigervnc.rfb.*; import com.tigervnc.network.*; import static com.tigervnc.vncviewer.Parameters.*; 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-2018 TigerVNC Team and many others (see README.rst)%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(Locale.ENGLISH); private static VncViewer applet; private String defaultServerName; int VNCSERVERNAMELEN = 64; CharBuffer vncServerName = CharBuffer.allocate(VNCSERVERNAMELEN); 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() { // Only called in applet mode this(new String[0]); } public VncViewer(String[] argv) { 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(); /* Load the default parameter settings */ try { defaultServerName = loadViewerParameters(null); } catch (com.tigervnc.rfb.Exception e) { defaultServerName = ""; vlog.info(e.getMessage()); } // Override defaults with command-line options int i = 0; for (; i < argv.length; i++) { if (argv[i].length() == 0) continue; if (argv[i].equalsIgnoreCase("-config")) { if (++i >= argv.length) usage(); defaultServerName = loadViewerParameters(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 (argv[i].charAt(0) == '-') { if (i+1 < argv.length) { if (Configuration.setParam(argv[i].substring(1), argv[i+1])) { i++; continue; } } if (Configuration.setParam(argv[i])) continue; usage(); } vncServerName.put(argv[i].toCharArray()).flip(); } } 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 static void newViewer() { String cmd = "java -jar "; try { URL url = VncViewer.class.getProtectionDomain().getCodeSource().getLocation(); File f = new File(url.toURI()); if (!f.exists() || !f.canRead()) { String msg = new String("The jar file "+f.getAbsolutePath()+ " does not exist or cannot be read."); JOptionPane.showMessageDialog(null, msg, "ERROR", JOptionPane.ERROR_MESSAGE); return; } cmd = cmd.concat(f.getAbsolutePath()); Thread t = new Thread(new ExtProcess(cmd, vlog)); t.start(); } catch (java.net.URISyntaxException e) { vlog.info(e.getMessage()); } catch (java.lang.Exception e) { vlog.info(e.getMessage()); } } 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() { JFrame f = (JFrame)JOptionPane.getFrameForComponent(this); if (f != null) f.dispose(); } public void setAppletCloseListener(ActionListener cl) { cc.setCloseListener(cl); } public void appletRestored() { cc.setCloseListener(null); } public static void setupEmbeddedFrame(JScrollPane sp) { InputMap im = sp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK; if (im != null) { im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, ctrlAltShiftMask), "unitScrollUp"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, ctrlAltShiftMask), "unitScrollDown"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, ctrlAltShiftMask), "unitScrollLeft"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, ctrlAltShiftMask), "unitScrollRight"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, ctrlAltShiftMask), "scrollUp"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, ctrlAltShiftMask), "scrollDown"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, ctrlAltShiftMask), "scrollLeft"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, ctrlAltShiftMask), "scrollRight"); } applet.getContentPane().removeAll(); applet.getContentPane().add(sp); applet.validate(); } public void init() { // Called right after zero-arg constructor in applet mode setLookAndFeel(); setBackground(Color.white); applet = this; vncServerName.put(loadAppletParameters(applet).toCharArray()).flip(); if (embed.getValue()) { fullScreen.setParam(false); remoteResize.setParam(false); maximize.setParam(false); scalingFactor.setParam("100"); } setFocusTraversalKeysEnabled(false); addFocusListener(new FocusAdapter() { public void focusGained(FocusEvent e) { if (cc != null && cc.desktop != null) cc.desktop.viewport.requestFocusInWindow(); } }); Frame frame = (Frame)getFocusCycleRootAncestor(); frame.setFocusTraversalKeysEnabled(false); frame.addWindowListener(new WindowAdapter() { // Transfer focus to scrollpane when browser receives it public void windowActivated(WindowEvent e) { if (cc != null && cc.desktop != null) cc.desktop.viewport.requestFocusInWindow(); } public void windowDeactivated(WindowEvent e) { if (cc != null) cc.desktop.viewport.releaseDownKeys(); } }); } private static 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 static void about_vncviewer(Container parent) { String pkgDate = ""; String pkgTime = ""; try { Manifest manifest = new Manifest(VncViewer.timestamp); Attributes attributes = manifest.getMainAttributes(); pkgDate = attributes.getValue("Package-Date"); pkgTime = attributes.getValue("Package-Time"); } catch (java.lang.Exception e) { } Window fullScreenWindow = DesktopWindow.getFullScreenWindow(); if (fullScreenWindow != null) DesktopWindow.setFullScreenWindow(null); String msg = String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build, VncViewer.buildDate, VncViewer.buildTime); Object[] options = {"Close \u21B5"}; JOptionPane op = new JOptionPane(msg, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, VncViewer.logoIcon, options); JDialog dlg = op.createDialog(parent, "About TigerVNC Viewer for Java"); dlg.setIconImage(VncViewer.frameIcon); dlg.setAlwaysOnTop(true); dlg.setVisible(true); if (fullScreenWindow != null) DesktopWindow.setFullScreenWindow(fullScreenWindow); } public void start() { (new Thread(this, "VncViewer Thread")).start(); } public void exit(int n) { 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); } } public void run() { cc = null; UserDialog dlg = new UserDialog(); CSecurity.upg = dlg; CSecurityTLS.msg = dlg; Socket sock = null; /* Specifying -via and -listen together is nonsense */ if (listenMode.getValue() && !via.getValueStr().isEmpty()) { vlog.error("Parameters -listen and -via are incompatible"); String msg = new String("Parameters -listen and -via are incompatible"); JOptionPane.showMessageDialog(null, msg, "ERROR", JOptionPane.ERROR_MESSAGE); exit(1); } if (listenMode.getValue()) { int port = 5500; if (vncServerName.charAt(0) != 0 && Character.isDigit(vncServerName.charAt(0))) port = Integer.parseInt(vncServerName.toString()); 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 (sock == null) sock = listener.accept(); } else { if (vncServerName.charAt(0) == 0) { try { SwingUtilities.invokeAndWait( new ServerDialog(defaultServerName, vncServerName)); } catch (InvocationTargetException e) { reportException(e); } catch (InterruptedException e) { reportException(e); } if (vncServerName.charAt(0) == 0) exit(0); } } try { cc = new CConn(vncServerName.toString(), sock); while (!cc.shuttingDown) cc.processMsg(); exit(0); } catch (java.lang.Exception e) { if (cc == null || !cc.shuttingDown) { reportException(e); if (cc != null) cc.close(); } else if (embed.getValue()) { reportException(new java.lang.Exception("Connection closed")); exit(0); } exit(1); } } public static CConn cc; public static StringParameter config = new StringParameter("Config", "Specifies a configuration file to load.", null); static LogWriter vlog = new LogWriter("VncViewer"); }