Mercurial > hg > Applications > TreeVNC
diff src/viewer_swing/java/com/glavsoft/viewer/Viewer.java @ 0:4689cc86d6cb
create TreeViewer2 Repository
author | Yu Taninari <you@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 03 Jul 2012 13:20:49 +0900 |
parents | |
children | 1a30763734cf 17b702648079 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/viewer_swing/java/com/glavsoft/viewer/Viewer.java Tue Jul 03 13:20:49 2012 +0900 @@ -0,0 +1,603 @@ +// Copyright (C) 2010, 2011 GlavSoft LLC. +// All rights reserved. +// +//------------------------------------------------------------------------- +// This file is part of the TightVNC software. Please visit our Web site: +// +// http://www.tightvnc.com/ +// +// This program 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 program 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 program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +//------------------------------------------------------------------------- +// + +package com.glavsoft.viewer; + +import com.glavsoft.core.SettingsChangedEvent; +import com.glavsoft.exceptions.*; +import com.glavsoft.rfb.IChangeSettingsListener; +import com.glavsoft.rfb.IPasswordRetriever; +import com.glavsoft.rfb.IRfbSessionListener; +import com.glavsoft.rfb.client.KeyEventMessage; +import com.glavsoft.rfb.protocol.Protocol; +import com.glavsoft.rfb.protocol.ProtocolContext; +import com.glavsoft.rfb.protocol.ProtocolSettings; +import com.glavsoft.transport.Reader; +import com.glavsoft.transport.Writer; +import com.glavsoft.utils.Keymap; +import com.glavsoft.utils.Strings; +import com.glavsoft.viewer.cli.Parser; +import com.glavsoft.viewer.swing.*; +import com.glavsoft.viewer.swing.gui.OptionsDialog; +import com.glavsoft.viewer.swing.gui.PasswordDialog; + +import javax.swing.*; +import java.awt.*; +import java.awt.Dialog.ModalityType; +import java.awt.event.*; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.LinkedList; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.logging.Logger; + +@SuppressWarnings("serial") +public class Viewer extends JApplet implements Runnable, IRfbSessionListener, WindowListener, + IChangeSettingsListener { + public static final int DEFAULT_PORT = 5900; + + public static Logger logger = Logger.getLogger("com.glavsoft"); + private static final Insets BUTTONS_MARGIN = new Insets(2, 2, 2, 2); + private boolean isZoomToFitSelected; + private boolean forceReconnection; + private String reconnectionReason; + private ContainerManager containerManager; + + public Protocol getWorkingProtocol() { + return workingProtocol; + } + + public boolean isZoomToFitSelected() { + return isZoomToFitSelected; + } + + public Surface getSurface() { + return surface; + } + + public UiSettings getUiSettings() { + return uiSettings; + } + + public void setZoomToFitSelected(boolean zoomToFitSelected) { + isZoomToFitSelected = zoomToFitSelected; + } + + /** + * Ask user for password if needed + */ + public class PasswordChooser implements IPasswordRetriever { + private final String passwordPredefined; + private final ParametersHandler.ConnectionParams connectionParams; + PasswordDialog passwordDialog; + private final JFrame owner; + private final WindowListener onClose; + + public PasswordChooser(String passwordPredefined, ParametersHandler.ConnectionParams connectionParams, + JFrame owner, WindowListener onClose) { + this.passwordPredefined = passwordPredefined; + this.connectionParams = connectionParams; + this.owner = owner; + this.onClose = onClose; + } + + @Override + public String getPassword() { + return Strings.isTrimmedEmpty(passwordPredefined) ? + getPasswordFromGUI() : + passwordPredefined; + } + + private String getPasswordFromGUI() { + if (null == passwordDialog) { + passwordDialog = new PasswordDialog(owner, onClose, isApplet); + } + passwordDialog.setServerHostName(connectionParams.hostName); + passwordDialog.setVisible(true); + return passwordDialog.getPassword(); + } + } + + public static void main(String[] args) { + Parser parser = new Parser(); + ParametersHandler.completeParserOptions(parser); + + parser.parse(args); + if (parser.isSet(ParametersHandler.ARG_HELP)) { + printUsage(parser.optionsUsage()); + System.exit(0); + } + Viewer viewer = new Viewer(parser); + SwingUtilities.invokeLater(viewer); + } + + public static void printUsage(String additional) { + System.out.println("Usage: java -jar (progfilename) [hostname [port_number]] [Options]\n" + + " or\n"+ + " java -jar (progfilename) [Options]\n" + + " or\n java -jar (progfilename) -help\n to view this help\n\n" + + "Where Options are:\n" + additional + + "\nOptions format: -optionName=optionValue. Ex. -host=localhost -port=5900 -viewonly=yes\n" + + "Both option name and option value are case insensitive."); + } + + + private final ParametersHandler.ConnectionParams connectionParams; + protected String passwordFromParams; + protected Socket workingSocket; + protected Protocol workingProtocol; + protected JFrame containerFrame; + boolean isSeparateFrame = true; + protected boolean isApplet = true; + protected boolean showControls = true; + protected Surface surface; + protected final ProtocolSettings settings; + protected final UiSettings uiSettings; + protected boolean tryAgain; + private boolean isAppletStopped = false; + private volatile boolean isStoppingProcess; + private List<JComponent> kbdButtons; + + public Viewer() { + connectionParams = new ParametersHandler.ConnectionParams(); + settings = ProtocolSettings.getDefaultSettings(); + uiSettings = new UiSettings(); + } + + private Viewer(Parser parser) { + this(); + ParametersHandler.completeSettingsFromCLI(parser, connectionParams, settings, uiSettings); + showControls = ParametersHandler.showControls; + passwordFromParams = parser.getValueFor(ParametersHandler.ARG_PASSWORD); + logger.info("TightVNC Viewer version " + ver()); + isApplet = false; + } + + + @Override + public void rfbSessionStopped(final String reason) { + if (isStoppingProcess) + return; + cleanUpUISessionAndConnection(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + forceReconnection = true; + reconnectionReason = reason; + } + }); + // start new session + SwingUtilities.invokeLater(this); + } + + private synchronized void cleanUpUISessionAndConnection() { + isStoppingProcess = true; + if (workingSocket != null && workingSocket.isConnected()) { + try { + workingSocket.close(); + } catch (IOException e) { /*nop*/ } + } + if (containerFrame != null) { + containerFrame.dispose(); + containerFrame = null; + } + isStoppingProcess = false; + } + + @Override + public void windowClosing(WindowEvent e) { + if (e != null && e.getComponent() != null) { + e.getWindow().setVisible(false); + } + closeApp(); + } + + /** + * Closes App(lication) or stops App(let). + */ + protected void closeApp() { + if (workingProtocol != null) { + workingProtocol.cleanUpSession(); + } + cleanUpUISessionAndConnection(); + tryAgain = false; + if (isApplet) { + logger.severe("Applet is stopped."); + isAppletStopped = true; + repaint(); + } else { + System.exit(0); + } + } + + @Override + public void paint(Graphics g) { + if ( ! isAppletStopped) { + super.paint(g); + } else { + getContentPane().removeAll(); + g.clearRect(0, 0, getWidth(), getHeight()); + g.drawString("Disconnected", 10, 20); + } + } + + @Override + public void destroy() { + closeApp(); + super.destroy(); + } + + @Override + public void init() { + ParametersHandler.completeSettingsFromApplet(this, connectionParams, settings, uiSettings); + showControls = ParametersHandler.showControls; + isSeparateFrame = ParametersHandler.isSeparateFrame; + passwordFromParams = getParameter(ParametersHandler.ARG_PASSWORD); + isApplet = true; + + repaint(); + SwingUtilities.invokeLater(this); + } + + @Override + public void start() { + setSurfaceToHandleKbdFocus(); + super.start(); + } + + @Override + public void run() { + ConnectionManager connectionManager = new ConnectionManager(this, isApplet); + + if (forceReconnection) { + connectionManager.showReconnectDialog("Connection lost", reconnectionReason); + forceReconnection = false; + } + tryAgain = true; + while (tryAgain) { + workingSocket = connectionManager.connectToHost(connectionParams, settings); + if (null == workingSocket) { + closeApp(); + break; + } + logger.info("Connected"); + + try { + workingSocket.setTcpNoDelay(true); // disable Nagle algorithm + Reader reader = new Reader(workingSocket.getInputStream()); + Writer writer = new Writer(workingSocket.getOutputStream()); + + workingProtocol = new Protocol(reader, writer, + new PasswordChooser(passwordFromParams, connectionParams, containerFrame, this), + settings); + workingProtocol.handshake(); + + ClipboardControllerImpl clipboardController = + new ClipboardControllerImpl(workingProtocol, settings.getRemoteCharsetName()); + clipboardController.setEnabled(settings.isAllowClipboardTransfer()); + settings.addListener(clipboardController); + + surface = new Surface(workingProtocol, this, uiSettings.getScaleFactor()); + settings.addListener(this); + uiSettings.addListener(surface); + containerFrame = createContainer(); + connectionManager.setContainerFrame(containerFrame); + updateFrameTitle(); + + workingProtocol.startNormalHandling(this, surface, clipboardController); + tryAgain = false; + } catch (UnsupportedProtocolVersionException e) { + connectionManager.showReconnectDialog("Unsupported Protocol Version", e.getMessage()); + logger.severe(e.getMessage()); + } catch (UnsupportedSecurityTypeException e) { + connectionManager.showReconnectDialog("Unsupported Security Type", e.getMessage()); + logger.severe(e.getMessage()); + } catch (AuthenticationFailedException e) { + passwordFromParams = null; + connectionManager.showReconnectDialog("Authentication Failed", e.getMessage()); + logger.severe(e.getMessage()); + } catch (TransportException e) { + connectionManager.showReconnectDialog("Connection Error", "Connection Error" + ": " + e.getMessage()); + logger.severe(e.getMessage()); + } catch (IOException e) { + connectionManager.showReconnectDialog("Connection Error", "Connection Error" + ": " + e.getMessage()); + logger.severe(e.getMessage()); + } catch (FatalException e) { + connectionManager.showReconnectDialog("Connection Error", "Connection Error" + ": " + e.getMessage()); + logger.severe(e.getMessage()); + } + } + } + + protected JFrame createContainer() { + containerManager = new ContainerManager(this); + Container container = containerManager.createContainer(surface, isSeparateFrame, isApplet); + + if (showControls) { + createButtonsPanel(container, workingProtocol, containerManager); + containerManager.registerResizeListener(container); + containerManager.updateZoomButtonsState(); + } + setSurfaceToHandleKbdFocus(); + return isSeparateFrame ? (JFrame)container : null; + } + + public void packContainer() { + containerManager.pack(); + } + + protected void createButtonsPanel(final Container container, final ProtocolContext context, ContainerManager containerManager) { + JPanel buttonBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 1)); + + JButton optionsButton = new JButton(Utils.getButtonIcon("options")); + optionsButton.setToolTipText("Set Options"); + optionsButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(optionsButton); + optionsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showOptionsDialog(); + setSurfaceToHandleKbdFocus(); + } + }); + + JButton infoButton = new JButton(Utils.getButtonIcon("info")); + infoButton.setToolTipText("Show connection info"); + infoButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(infoButton); + infoButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showConnectionInfoMessage(context.getRemoteDesktopName()); + setSurfaceToHandleKbdFocus(); + } + }); + + buttonBar.add(Box.createHorizontalStrut(10)); + JButton refreshButton = new JButton(Utils.getButtonIcon("refresh")); + refreshButton.setToolTipText("Refresh screen"); + refreshButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(refreshButton); + refreshButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + context.sendRefreshMessage(); + setSurfaceToHandleKbdFocus(); + } + }); + + containerManager.addZoomButtons(buttonBar, BUTTONS_MARGIN); + + kbdButtons = new LinkedList<JComponent>(); + buttonBar.add(Box.createHorizontalStrut(10)); + JButton ctrlAltDelButton = new JButton(Utils.getButtonIcon("ctrl-alt-del")); + ctrlAltDelButton.setToolTipText("Send 'Ctrl-Alt-Del'"); + ctrlAltDelButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(ctrlAltDelButton); + kbdButtons.add(ctrlAltDelButton); + ctrlAltDelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + sendCtrlAltDel(context); + setSurfaceToHandleKbdFocus(); + } + }); + + JButton winButton = new JButton(Utils.getButtonIcon("win")); + winButton.setToolTipText("Send 'Win' key as 'Ctrl-Esc'"); + winButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(winButton); + kbdButtons.add(winButton); + winButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + sendWinKey(context); + setSurfaceToHandleKbdFocus(); + } + }); + + JToggleButton ctrlButton = new JToggleButton(Utils.getButtonIcon("ctrl")); + ctrlButton.setToolTipText("Ctrl Lock"); + ctrlButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(ctrlButton); + kbdButtons.add(ctrlButton); + ctrlButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setSurfaceToHandleKbdFocus(); + } + }); + ctrlButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true)); + } else { + context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false)); + } + } + }); + + JToggleButton altButton = new JToggleButton(Utils.getButtonIcon("alt")); + kbdButtons.add(altButton); + altButton.setToolTipText("Alt Lock"); + altButton.setMargin(BUTTONS_MARGIN); + buttonBar.add(altButton); + kbdButtons.add(altButton); + altButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, true)); + } else { + context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, false)); + } + } + }); + altButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setSurfaceToHandleKbdFocus(); + } + }); + ModifierButtonEventListener modifierButtonListener = new ModifierButtonEventListener(); + modifierButtonListener.addButton(KeyEvent.VK_CONTROL, ctrlButton); + modifierButtonListener.addButton(KeyEvent.VK_ALT, altButton); + surface.addModifierListener(modifierButtonListener); + +// JButton fileTransferButton = new JButton(Utils.getButtonIcon("file-transfer")); +// fileTransferButton.setMargin(buttonsMargin); +// buttonBar.add(fileTransferButton); + + buttonBar.add(Box.createHorizontalStrut(10)); + + JButton closeButton = new JButton(Utils.getButtonIcon("close")); + closeButton.setToolTipText(isApplet ? "Disconnect" : "Close"); + closeButton.setMargin(BUTTONS_MARGIN); + closeButton.setAlignmentX(RIGHT_ALIGNMENT); + buttonBar.add(closeButton); + closeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + closeApp(); + } + }); + + container.add(buttonBar, BorderLayout.NORTH); + } + + protected void updateFrameTitle() { + if (containerFrame != null) { + containerFrame.setTitle( + workingProtocol.getRemoteDesktopName() + " [zoom: " + uiSettings.getScalePercentFormatted() + "%]"); + } + } + + protected void setSurfaceToHandleKbdFocus() { + if (surface != null && ! surface.requestFocusInWindow()) { + surface.requestFocus(); + } + } + + @Override + public void settingsChanged(SettingsChangedEvent e) { + ProtocolSettings settings = (ProtocolSettings) e.getSource(); + setEnabledKbdButtons( ! settings.isViewOnly()); + } + + private void setEnabledKbdButtons(boolean enabled) { + if (kbdButtons != null) { + for (JComponent b : kbdButtons) { + b.setEnabled(enabled); + } + } + } + + private void showOptionsDialog() { + OptionsDialog optionsDialog = new OptionsDialog(containerFrame); + optionsDialog.initControlsFromSettings(settings, false); + optionsDialog.setVisible(true); + } + + private void showConnectionInfoMessage(final String title) { + StringBuilder message = new StringBuilder(); + message.append("Connected to: ").append(title).append("\n"); + message.append("Host: ").append(connectionParams.hostName) + .append(" Port: ").append(connectionParams.portNumber).append("\n\n"); + + message.append("Desktop geometry: ") + .append(String.valueOf(surface.getWidth())) + .append(" \u00D7 ") // multiplication sign + .append(String.valueOf(surface.getHeight())).append("\n"); + message.append("Color format: ") + .append(String.valueOf(Math.round(Math.pow(2, workingProtocol.getPixelFormat().depth)))) + .append(" colors (") + .append(String.valueOf(workingProtocol.getPixelFormat().depth)) + .append(" bits)\n"); + message.append("Current protocol version: ") + .append(settings.getProtocolVersion()); + if (settings.isTight()) { + message.append("tight"); + } + message.append("\n"); + + JOptionPane infoPane = new JOptionPane(message.toString(), JOptionPane.INFORMATION_MESSAGE); + final JDialog infoDialog = infoPane.createDialog(containerFrame, "VNC connection info"); + infoDialog.setModalityType(ModalityType.MODELESS); + try { + infoDialog.setAlwaysOnTop(true); + } catch (SecurityException e) { /*nop*/ } + infoDialog.setVisible(true); + } + + private void sendCtrlAltDel(ProtocolContext context) { + context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true)); + context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, true)); + context.sendMessage(new KeyEventMessage(Keymap.K_DELETE, true)); + context.sendMessage(new KeyEventMessage(Keymap.K_DELETE, false)); + context.sendMessage(new KeyEventMessage(Keymap.K_ALT_LEFT, false)); + context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false)); + } + + private void sendWinKey(ProtocolContext context) { + context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, true)); + context.sendMessage(new KeyEventMessage(Keymap.K_ESCAPE, true)); + context.sendMessage(new KeyEventMessage(Keymap.K_ESCAPE, false)); + context.sendMessage(new KeyEventMessage(Keymap.K_CTRL_LEFT, false)); + } + + @Override + public void windowOpened(WindowEvent e) { /* nop */ } + @Override + public void windowClosed(WindowEvent e) { /* nop */ } + @Override + public void windowIconified(WindowEvent e) { /* nop */ } + @Override + public void windowDeiconified(WindowEvent e) { /* nop */ } + @Override + public void windowActivated(WindowEvent e) { /* nop */ } + @Override + public void windowDeactivated(WindowEvent e) { /* nop */ } + + protected static String ver() { + final InputStream mfStream = Viewer.class.getClassLoader().getResourceAsStream( + "META-INF/MANIFEST.MF"); + if (null == mfStream) { + System.out.println("No Manifest file found."); + return "-1"; + } + try { + Manifest mf = new Manifest(); + mf.read(mfStream); + Attributes atts = mf.getMainAttributes(); + return atts.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + } catch (IOException e) { + return "-2"; + } + } + +}