Files
2026-07-28 03:21:34 +08:00

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");
}