470 lines
17 KiB
Java
470 lines
17 KiB
Java
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
|
|
* Copyright 2011 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.
|
|
*/
|
|
|
|
//
|
|
// VncViewer - the VNC viewer. It behaves as much as possible like the native
|
|
// 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.
|
|
|
|
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 implements Runnable {
|
|
|
|
public static final String aboutText =
|
|
new String("T2SRFBVNC Java viewer v%s (%s)%n"+
|
|
"Built on %s at %s%n"+
|
|
"Copyright (C) 1999-2025 T2SRFBVNC team and many others (see README.rst)%n"+
|
|
"See https://www.tigervnc.org for information on T2SRFBVNC.");
|
|
|
|
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 String defaultServerName;
|
|
int VNCSERVERNAMELEN = 256;
|
|
CharBuffer vncServerName = CharBuffer.allocate(VNCSERVERNAMELEN);
|
|
|
|
public static void setLookAndFeel() {
|
|
try {
|
|
if (os.startsWith("mac os x")) {
|
|
String appClassName = new String("com.apple.eawt.Application");
|
|
String appMethodName = new String("getApplication");
|
|
String setIconMethodName = new String("setDockIconImage");
|
|
// JEP-272. Platform-specific Desktop Features are strongly encapsulated
|
|
// in JRE 9 & above, but the API features needed aren't in JRE 8.
|
|
if (Float.parseFloat(System.getProperty("java.specification.version")) > 1.8) {
|
|
appClassName = new String("java.awt.Taskbar");
|
|
appMethodName = new String("getTaskbar");
|
|
setIconMethodName = new String("setIconImage");
|
|
}
|
|
Class appClass = Class.forName(appClassName);
|
|
Method getApplication =
|
|
appClass.getMethod(appMethodName, (Class[])null);
|
|
Object app = getApplication.invoke(appClass);
|
|
Class paramTypes[] = new Class[1];
|
|
paramTypes[0] = Image.class;
|
|
Method setDockIconImage =
|
|
appClass.getMethod(setIconMethodName, 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<Object> 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) {
|
|
|
|
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();
|
|
}
|
|
|
|
// Check if the server name in reality is a configuration file
|
|
potentiallyLoadConfigurationFile(vncServerName);
|
|
|
|
migrateDeprecatedOptions();
|
|
}
|
|
|
|
public static void usage() {
|
|
String usage = ("\nusage: vncviewer [options/parameters] "+
|
|
"[host:displayNum]\n"+
|
|
" vncviewer [options/parameters] -listen [port] "+
|
|
"[options/parameters]\n"+
|
|
" vncviewer [options/parameters] [.tigervnc file]\n"+
|
|
"\n"+
|
|
"Options:\n"+
|
|
" -log <level> configure logging level\n"+
|
|
"\n"+
|
|
"Parameters can be turned on with -<param> or off with "+
|
|
"-<param>=0\n"+
|
|
"Parameters which take a value can be specified as "+
|
|
"-<param> <value>\n"+
|
|
"Other valid forms are <param>=<value> -<param>=<value> "+
|
|
"--<param>=<value>\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 potentiallyLoadConfigurationFile(CharBuffer vncServerName) {
|
|
String serverName = vncServerName.toString();
|
|
boolean hasPathSeparator = (serverName.indexOf('/') != -1 ||
|
|
(serverName.indexOf('\\')) != -1);
|
|
|
|
if (hasPathSeparator) {
|
|
try {
|
|
serverName = loadViewerParameters(vncServerName.toString());
|
|
if (serverName == "") {
|
|
vlog.info("Unable to load the server name from given file");
|
|
System.exit(1);
|
|
}
|
|
vncServerName.clear();
|
|
vncServerName.put(serverName).flip();
|
|
} catch (com.tigervnc.rfb.Exception e) {
|
|
vlog.info(e.getMessage());
|
|
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());
|
|
}
|
|
}
|
|
|
|
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 T2SRFBVNC");
|
|
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) {
|
|
System.exit(n);
|
|
}
|
|
|
|
void reportException(java.lang.Exception e) {
|
|
String title, msg = e.getMessage();
|
|
int msgType = JOptionPane.ERROR_MESSAGE;
|
|
title = "T2SRFBVNC : Error";
|
|
e.printStackTrace();
|
|
JOptionPane.showMessageDialog(null, msg, title, msgType);
|
|
}
|
|
|
|
public void run() {
|
|
cc = null;
|
|
UserDialog dlg = new UserDialog();
|
|
CSecurity.upg = dlg;
|
|
CSecurity.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();
|
|
}
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void migrateDeprecatedOptions() {
|
|
if (dotWhenNoCursor.getValue()) {
|
|
vlog.info("DotWhenNoCursor is deprecated, set AlwaysCursor to 1 and CursorType to 'Dot' instead");
|
|
|
|
alwaysCursor.setParam(true);
|
|
cursorType.setParam("Dot");
|
|
}
|
|
}
|
|
|
|
public static CConn cc;
|
|
public static StringParameter config
|
|
= new StringParameter("Config",
|
|
"Specifies a configuration file to load.", null);
|
|
|
|
static LogWriter vlog = new LogWriter("VncViewer");
|
|
}
|