Java tutorial
/******************************************************************************* * Copyright (c) 2003, 2011 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Initial Contributors: * The following Wind River employees contributed to the Terminal component * that contains this file: Chris Thew, Fran Litterio, Stephen Lamb, * Helmut Haigermoser and Ted Williams. * * Contributors: * Michael Scharf (Wind River) - split into core, view and connector plugins * Martin Oberhuber (Wind River) - fixed copyright headers and beautified * Martin Oberhuber (Wind River) - [206892] State handling: Only allow connect when CLOSED * Martin Oberhuber (Wind River) - [206883] Serial Terminal leaks Jobs * Martin Oberhuber (Wind River) - [208145] Terminal prints garbage after quick disconnect/reconnect * Martin Oberhuber (Wind River) - [207785] NPE when trying to send char while no longer connected * Michael Scharf (Wind River) - [209665] Add ability to log byte streams from terminal * Ruslan Sychev (Xored Software) - [217675] NPE or SWTException when closing Terminal View while connection establishing * Michael Scharf (Wing River) - [196447] The optional terminal input line should be resizeable * Martin Oberhuber (Wind River) - [168197] Replace JFace MessagDialog by SWT MessageBox * Martin Oberhuber (Wind River) - [204796] Terminal should allow setting the encoding to use * Michael Scharf (Wind River) - [237398] Terminal get Invalid Thread Access when the title is set * Martin Oberhuber (Wind River) - [240745] Pressing Ctrl+F1 in the Terminal should bring up context help * Michael Scharf (Wind River) - [240098] The cursor should not blink when the terminal is disconnected * Anton Leherbauer (Wind River) - [335021] Middle mouse button copy/paste does not work with the terminal * Max Stepanov (Appcelerator) - [339768] Fix ANSI code for PgUp / PgDn *******************************************************************************/ package org.eclipse.tm.internal.terminal.emulator; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.SocketException; import java.util.Arrays; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.bindings.keys.KeyStroke; import org.eclipse.jface.bindings.keys.SWTKeySupport; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import org.eclipse.tm.internal.terminal.control.ICommandInputField; import org.eclipse.tm.internal.terminal.control.ITerminalListener; import org.eclipse.tm.internal.terminal.control.ITerminalViewControl; import org.eclipse.tm.internal.terminal.control.impl.ITerminalControlForText; import org.eclipse.tm.internal.terminal.control.impl.TerminalMessages; import org.eclipse.tm.internal.terminal.control.impl.TerminalPlugin; import org.eclipse.tm.internal.terminal.provisional.api.ITerminalConnector; import org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl; import org.eclipse.tm.internal.terminal.provisional.api.Logger; import org.eclipse.tm.internal.terminal.provisional.api.TerminalState; import org.eclipse.tm.internal.terminal.textcanvas.ILinelRenderer; import org.eclipse.tm.internal.terminal.textcanvas.ITextCanvasModel; import org.eclipse.tm.internal.terminal.textcanvas.PipedInputStream; import org.eclipse.tm.internal.terminal.textcanvas.PollingTextCanvasModel; import org.eclipse.tm.internal.terminal.textcanvas.TextCanvas; import org.eclipse.tm.internal.terminal.textcanvas.TextLineRenderer; import org.eclipse.tm.terminal.model.ITerminalTextData; import org.eclipse.tm.terminal.model.ITerminalTextDataSnapshot; import org.eclipse.tm.terminal.model.TerminalTextDataFactory; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextActivation; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.keys.IBindingService; /** * * This class was originally written to use nested classes, which unfortunately makes * this source file larger and more complex than it needs to be. In particular, the * methods in the nested classes directly access the fields of the enclosing class. * One day we should pull the nested classes out into their own source files (but still * in this package). * * @author Chris Thew <chris.thew@windriver.com> */ public class VT100TerminalControl implements ITerminalControlForText, ITerminalControl, ITerminalViewControl { protected final static String[] LINE_DELIMITERS = { "\n" }; //$NON-NLS-1$ protected final static int[] BYPASS_ACCELERATORS = new int[] { SWTKeySupport.convertKeyStrokeToAccelerator( KeyStroke.getInstance(SWT.COMMAND == SWT.MOD1 ? SWT.MOD1 : SWT.MOD1 | SWT.MOD2, 'C')), SWTKeySupport.convertKeyStrokeToAccelerator( KeyStroke.getInstance(SWT.COMMAND == SWT.MOD1 ? SWT.MOD1 : SWT.MOD1 | SWT.MOD2, 'V')), SWTKeySupport.convertKeyStrokeToAccelerator( KeyStroke.getInstance(SWT.COMMAND == SWT.MOD1 ? SWT.MOD1 : SWT.MOD1 | SWT.MOD2, 'A')), SWTKeySupport.convertKeyStrokeToAccelerator(KeyStroke.getInstance(SWT.CONTROL, SWT.TAB)), SWTKeySupport.convertKeyStrokeToAccelerator(KeyStroke.getInstance(SWT.CONTROL | SWT.SHIFT, SWT.TAB)) }; /** * This field holds a reference to a TerminalText object that performs all ANSI * text processing on data received from the remote host and controls how text is * displayed using the view's StyledText widget. */ private final VT100Emulator fTerminalText; private Display fDisplay; private TextCanvas fCtlText; private Composite fWndParent; private Clipboard fClipboard; private KeyListener fKeyHandler; private final ITerminalListener fTerminalListener; private String fMsg = ""; //$NON-NLS-1$ private FocusListener fFocusListener; private ITerminalConnector fConnector; private final ITerminalConnector[] fConnectors; PipedInputStream fInputStream; private static final String defaultEncoding = new java.io.InputStreamReader( new java.io.ByteArrayInputStream(new byte[0])).getEncoding(); private String fEncoding = defaultEncoding; private InputStreamReader fInputStreamReader; private ICommandInputField fCommandInputField; private volatile TerminalState fState; private boolean isApplicationKeypad = false; private final ITerminalTextData fTerminalModel; /** * Is protected by synchronize on this */ volatile private Job fJob; public VT100TerminalControl(ITerminalListener target, Composite wndParent, ITerminalConnector[] connectors) { fConnectors = connectors; fTerminalListener = target; fTerminalModel = TerminalTextDataFactory.makeTerminalTextData(); fTerminalModel.setMaxHeight(1000); fInputStream = new PipedInputStream(8 * 1024); fTerminalText = new VT100Emulator(fTerminalModel, this, null); try { // Use Default Encoding as start, until setEncoding() is called setEncoding(null); } catch (UnsupportedEncodingException e) { // Should never happen e.printStackTrace(); // Fall back to local Platform Default Encoding fEncoding = defaultEncoding; fInputStreamReader = new InputStreamReader(fInputStream); fTerminalText.setInputStreamReader(fInputStreamReader); } setupTerminal(wndParent); } public void setEncoding(String encoding) throws UnsupportedEncodingException { if (encoding == null) { // TODO better use a standard remote-to-local encoding? encoding = "ISO-8859-1"; //$NON-NLS-1$ // TODO or better use the local default encoding? // encoding = defaultEncoding; } fInputStreamReader = new InputStreamReader(fInputStream, encoding); // remember encoding if above didn't throw an exception fEncoding = encoding; fTerminalText.setInputStreamReader(fInputStreamReader); } public String getEncoding() { return fEncoding; } public ITerminalConnector[] getConnectors() { return fConnectors; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#copy() */ public void copy() { copy(DND.CLIPBOARD); } private void copy(int clipboardType) { Object[] data = new Object[] { getSelection() }; Transfer[] types = new Transfer[] { TextTransfer.getInstance() }; fClipboard.setContents(data, types, clipboardType); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#paste() */ public void paste() { paste(DND.CLIPBOARD); // TODO paste in another thread.... to avoid blocking // new Thread() { // public void run() { // for (int i = 0; i < strText.length(); i++) { // sendChar(strText.charAt(i), false); // } // // } // }.start(); } private void paste(int clipboardType) { TextTransfer textTransfer = TextTransfer.getInstance(); String strText = (String) fClipboard.getContents(textTransfer, clipboardType); pasteString(strText); } /** * @param strText the text to paste */ public boolean pasteString(String strText) { if (!isConnected()) return false; if (strText == null) return false; if (!fEncoding.equals(defaultEncoding)) { sendString(strText); } else { // TODO I do not understand why pasteString would do this here... for (int i = 0; i < strText.length(); i++) { sendChar(strText.charAt(i), false); } } return true; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#selectAll() */ public void selectAll() { getCtlText().selectAll(); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#sendKey(char) */ public void sendKey(char character) { Event event; KeyEvent keyEvent; event = new Event(); event.widget = getCtlText(); event.character = character; event.keyCode = 0; event.stateMask = 0; event.doit = true; keyEvent = new KeyEvent(event); fKeyHandler.keyPressed(keyEvent); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#clearTerminal() */ public void clearTerminal() { // The TerminalText object does all text manipulation. getTerminalText().clearTerminal(); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getClipboard() */ public Clipboard getClipboard() { return fClipboard; } /** * @return non null selection */ public String getSelection() { String txt = fCtlText.getSelectionText(); if (txt == null) txt = ""; //$NON-NLS-1$ return txt; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#setFocus() */ public boolean setFocus() { return getCtlText().setFocus(); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#isEmpty() */ public boolean isEmpty() { return getCtlText().isEmpty(); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#isDisposed() */ public boolean isDisposed() { return getCtlText().isDisposed(); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#isConnected() */ public boolean isConnected() { return fState == TerminalState.CONNECTED; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#disposeTerminal() */ public void disposeTerminal() { Logger.log("entered."); //$NON-NLS-1$ disconnectTerminal(); fClipboard.dispose(); getTerminalText().dispose(); } public void connectTerminal() { Logger.log("entered."); //$NON-NLS-1$ if (getTerminalConnector() == null) return; fTerminalText.resetState(); if (fConnector.getInitializationErrorMessage() != null) { showErrorMessage(NLS.bind(TerminalMessages.CannotConnectTo, fConnector.getName(), fConnector.getInitializationErrorMessage())); // we cannot connect because the connector was not initialized return; } getTerminalConnector().connect(this); // clean the error message setMsg(""); //$NON-NLS-1$ waitForConnect(); } public ITerminalConnector getTerminalConnector() { return fConnector; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#disconnectTerminal() */ public void disconnectTerminal() { Logger.log("entered."); //$NON-NLS-1$ if (getState() == TerminalState.CLOSED) { return; } if (getTerminalConnector() != null) { getTerminalConnector().disconnect(); } //Ensure that a new Job can be started; then clean up old Job. //TODO not sure whether the fInputStream needs to be cleaned too, //or whether the Job could actually cancel in case the fInputStream is closed. Job job; synchronized (this) { job = fJob; fJob = null; } if (job != null) { job.cancel(); //There's not really a need to interrupt, since the job will //check its cancel status after 500 msec latest anyways... //Thread t = job.getThread(); //if(t!=null) t.interrupt(); } } // TODO private void waitForConnect() { Logger.log("entered."); //$NON-NLS-1$ // TODO // Eliminate this code while (getState() == TerminalState.CONNECTING) { if (fDisplay.readAndDispatch()) continue; fDisplay.sleep(); } if (getCtlText().isDisposed()) { disconnectTerminal(); return; } if (!getMsg().equals("")) //$NON-NLS-1$ { showErrorMessage(getMsg()); disconnectTerminal(); return; } getCtlText().setFocus(); startReaderJob(); } private synchronized void startReaderJob() { if (fJob == null) { fJob = new Job("Terminal data reader") { //$NON-NLS-1$ protected IStatus run(IProgressMonitor monitor) { IStatus status = Status.OK_STATUS; try { while (true) { while (fInputStream.available() == 0 && !monitor.isCanceled()) { try { fInputStream.waitForAvailable(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (monitor.isCanceled()) { //Do not disconnect terminal here because another reader job may already be running status = Status.CANCEL_STATUS; break; } try { // TODO: should block when no text is available! fTerminalText.processText(); } catch (Exception e) { disconnectTerminal(); status = new Status(IStatus.ERROR, TerminalPlugin.PLUGIN_ID, e.getLocalizedMessage(), e); break; } } } finally { // clean the job: start a new one when the connection gets restarted // Bug 208145: make sure we do not clean an other job that's already started (since it would become a Zombie) synchronized (VT100TerminalControl.this) { if (fJob == this) { fJob = null; } } } return status; } }; fJob.setSystem(true); fJob.schedule(); } } private void showErrorMessage(String message) { String strTitle = TerminalMessages.TerminalError; // [168197] Replace JFace MessagDialog by SWT MessageBox //MessageDialog.openError( getShell(), strTitle, message); MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK); mb.setText(strTitle); mb.setMessage(message); mb.open(); } protected void sendString(String string) { try { // Send the string after converting it to an array of bytes using the // platform's default character encoding. // // TODO: Find a way to force this to use the ISO Latin-1 encoding. // TODO: handle Encoding Errors in a better way getOutputStream().write(string.getBytes(fEncoding)); getOutputStream().flush(); } catch (SocketException socketException) { displayTextInTerminal(socketException.getMessage()); String strMsg = TerminalMessages.SocketError + "!\n" + socketException.getMessage(); //$NON-NLS-1$ showErrorMessage(strMsg); Logger.logException(socketException); disconnectTerminal(); } catch (IOException ioException) { showErrorMessage(TerminalMessages.IOError + "!\n" + ioException.getMessage());//$NON-NLS-1$ Logger.logException(ioException); disconnectTerminal(); } } public Shell getShell() { return getCtlText().getShell(); } protected void sendChar(char chKey, boolean altKeyPressed) { try { int byteToSend = chKey; OutputStream os = getOutputStream(); if (os == null) { // Bug 207785: NPE when trying to send char while no longer connected Logger.log("NOT sending '" + byteToSend + "' because no longer connected"); //$NON-NLS-1$ //$NON-NLS-2$ } else { if (altKeyPressed) { // When the ALT key is pressed at the same time that a character is // typed, translate it into an ESCAPE followed by the character. The // alternative in this case is to set the high bit of the character // being transmitted, but that will cause input such as ALT-f to be // seen as the ISO Latin-1 character '', which can be confusing to // European users running Emacs, for whom Alt-f should move forward a // word instead of inserting the '' character. // // TODO: Make the ESCAPE-vs-highbit behavior user configurable. Logger.log("sending ESC + '" + byteToSend + "'"); //$NON-NLS-1$ //$NON-NLS-2$ getOutputStream().write('\u001b'); getOutputStream().write(byteToSend); } else if (byteToSend > 127) { byte[] bytesToSend = String.valueOf(chKey).getBytes(fEncoding); Logger.log("sending '" + Arrays.asList(bytesToSend) + "'"); //$NON-NLS-1$ //$NON-NLS-2$ getOutputStream().write(bytesToSend); } else { Logger.log("sending '" + byteToSend + "'"); //$NON-NLS-1$ //$NON-NLS-2$ getOutputStream().write(byteToSend); } getOutputStream().flush(); } } catch (SocketException socketException) { Logger.logException(socketException); displayTextInTerminal(socketException.getMessage()); String strMsg = TerminalMessages.SocketError + "!\n" + socketException.getMessage(); //$NON-NLS-1$ showErrorMessage(strMsg); Logger.logException(socketException); disconnectTerminal(); } catch (IOException ioException) { Logger.logException(ioException); displayTextInTerminal(ioException.getMessage()); String strMsg = TerminalMessages.IOError + "!\n" + ioException.getMessage(); //$NON-NLS-1$ showErrorMessage(strMsg); Logger.logException(ioException); disconnectTerminal(); } } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#setupTerminal() */ public void setupTerminal(Composite parent) { fState = TerminalState.CLOSED; setupControls(parent); setupListeners(); setupHelp(fWndParent, TerminalPlugin.HELP_VIEW); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#onFontChanged() */ public void setFont(Font font) { getCtlText().setFont(font); if (fCommandInputField != null) { fCommandInputField.setFont(font); } // Tell the TerminalControl singleton that the font has changed. fCtlText.onFontChange(); getTerminalText().fontChanged(); } public Font getFont() { return getCtlText().getFont(); } public Control getControl() { return fCtlText; } public Control getRootControl() { return fWndParent; } protected void setupControls(Composite parent) { // The Terminal view now aims to be an ANSI-conforming terminal emulator, so it // can't have a horizontal scroll bar (but a vertical one is ok). Also, do // _not_ make the TextViewer read-only, because that prevents it from seeing a // TAB character when the user presses TAB (instead, the TAB causes focus to // switch to another Workbench control). We prevent local keyboard input from // modifying the text in method TerminalVerifyKeyListener.verifyKey(). fWndParent = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); layout.marginWidth = 0; layout.marginHeight = 0; layout.verticalSpacing = 0; fWndParent.setLayout(layout); ITerminalTextDataSnapshot snapshot = fTerminalModel.makeSnapshot(); // TODO how to get the initial size correctly! snapshot.updateSnapshot(false); ITextCanvasModel canvasModel = new PollingTextCanvasModel(snapshot); fCtlText = createTextCanvas(fWndParent, canvasModel, createLineRenderer(canvasModel)); fCtlText.setLayoutData(new GridData(GridData.FILL_BOTH)); fCtlText.addResizeHandler(new TextCanvas.ResizeListener() { public void sizeChanged(int lines, int columns) { fTerminalText.setDimensions(lines, columns); } }); fCtlText.addMouseListener(new MouseAdapter() { public void mouseUp(MouseEvent e) { // update selection used by middle mouse button paste if (e.button == 1 && getSelection().length() > 0) { copy(DND.SELECTION_CLIPBOARD); } } }); fDisplay = getCtlText().getDisplay(); fClipboard = new Clipboard(fDisplay); // fViewer.setDocument(new TerminalDocument()); setFont(JFaceResources.getTextFont()); } protected TextCanvas createTextCanvas(Composite parent, ITextCanvasModel canvasModel, ILinelRenderer linelRenderer) { return new TextCanvas(parent, canvasModel, SWT.NONE, linelRenderer); } protected ILinelRenderer createLineRenderer(ITextCanvasModel model) { return new TextLineRenderer(null, model); } protected void setupListeners() { fKeyHandler = new TerminalKeyHandler(); fFocusListener = new TerminalFocusListener(); getCtlText().addKeyListener(fKeyHandler); getCtlText().addFocusListener(fFocusListener); } /** * Setup all the help contexts for the controls. */ protected void setupHelp(Composite parent, String id) { Control[] children = parent.getChildren(); for (int nIndex = 0; nIndex < children.length; nIndex++) { if (children[nIndex] instanceof Composite) { setupHelp((Composite) children[nIndex], id); } } PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, id); } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#displayTextInTerminal(java.lang.String) */ public void displayTextInTerminal(String text) { writeToTerminal("\r\n" + text + "\r\n"); //$NON-NLS-1$ //$NON-NLS-2$ } private void writeToTerminal(String text) { try { getRemoteToTerminalOutputStream().write(text.getBytes(fEncoding)); } catch (UnsupportedEncodingException e) { // should never happen! e.printStackTrace(); } catch (IOException e) { // should never happen! e.printStackTrace(); } } public OutputStream getRemoteToTerminalOutputStream() { if (Logger.isLogEnabled()) { return new LoggingOutputStream(fInputStream.getOutputStream()); } else { return fInputStream.getOutputStream(); } } protected boolean isLogCharEnabled() { return TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_CHAR); } protected boolean isLogBufferSizeEnabled() { return TerminalPlugin.isOptionEnabled(Logger.TRACE_DEBUG_LOG_BUFFER_SIZE); } public OutputStream getOutputStream() { if (getTerminalConnector() != null) return getTerminalConnector().getTerminalToRemoteStream(); return null; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#setMsg(java.lang.String) */ public void setMsg(String msg) { fMsg = msg; } public String getMsg() { return fMsg; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getCtlText() */ protected TextCanvas getCtlText() { return fCtlText; } /* (non-Javadoc) * @see org.eclipse.tm.internal.terminal.provisional.api.ITerminalControl#getTerminalText() */ public VT100Emulator getTerminalText() { return fTerminalText; } protected class TerminalFocusListener implements FocusListener { private IContextActivation contextActivation = null; protected TerminalFocusListener() { super(); } public void focusGained(FocusEvent event) { // Disable all keyboard accelerators (e.g., Control-B) so the Terminal view // can see every keystroke. Without this, Emacs, vi, and Bash are unusable // in the Terminal view. IBindingService bindingService = (IBindingService) PlatformUI.getWorkbench() .getAdapter(IBindingService.class); bindingService.setKeyFilterEnabled(false); // The above code fails to cause Eclipse to disable menu-activation // accelerators (e.g., Alt-F for the File menu), so we set the command // context to be the Terminal view's command context. This enables us to // override menu-activation accelerators with no-op commands in our // plugin.xml file, which enables the Terminal view to see absolutly _all_ // key-presses. IContextService contextService = (IContextService) PlatformUI.getWorkbench() .getAdapter(IContextService.class); contextActivation = contextService .activateContext("com.aptana.org.eclipse.tm.terminal.TerminalContext"); //$NON-NLS-1$ } public void focusLost(FocusEvent event) { // Enable all keybindings. IBindingService bindingService = (IBindingService) PlatformUI.getWorkbench() .getAdapter(IBindingService.class); bindingService.setKeyFilterEnabled(true); // Restore the command context to its previous value. IContextService contextService = (IContextService) PlatformUI.getWorkbench() .getAdapter(IContextService.class); contextService.deactivateContext(contextActivation); } } protected class TerminalKeyHandler extends KeyAdapter { public void keyPressed(KeyEvent event) { if (getState() == TerminalState.CONNECTING) return; int accelerator = SWTKeySupport.convertEventToUnmodifiedAccelerator(event); for (int i = 0; i < BYPASS_ACCELERATORS.length; ++i) { if (BYPASS_ACCELERATORS[i] == accelerator) { return; } } char character = event.character; if (character == '\u0000') { if ((event.stateMask & SWT.MOD1) != 0 && event.keyCode >= SWT.F1 && event.keyCode <= SWT.F15) { return; } } // We set the event.doit to false to prevent any further processing of this // key event. The only reason this is here is because I was seeing the F10 // key both send an escape sequence (due to this method) and switch focus // to the Workbench File menu (forcing the user to click in the Terminal // view again to continue entering text). This fixes that. event.doit = false; //if (!isConnected()) { if (fState == TerminalState.CLOSED) { // Pressing ENTER while not connected causes us to connect. if (character == '\r') { connectTerminal(); return; } // Ignore all other keyboard input when not connected. // Allow other key handlers (such as Ctrl+F1) do their work event.doit = true; return; } // If the event character is NUL ('\u0000'), then a special key was pressed // (e.g., PageUp, PageDown, an arrow key, a function key, Shift, Alt, // Control, etc.). The one exception is when the user presses Control-@, // which sends a NUL character, in which case we must send the NUL to the // remote endpoint. This is necessary so that Emacs will work correctly, // because Control-@ (i.e., NUL) invokes Emacs' set-mark-command when Emacs // is running on a terminal. When the user presses Control-@, the keyCode // is 50. if (character == '\u0000' && event.keyCode != 50) { // A special key was pressed. Figure out which one it was and send the // appropriate ANSI escape sequence. // // IMPORTANT: Control will not enter this method for these special keys // unless certain <keybinding> tags are present in the plugin.xml file // for the Terminal view. Do not delete those tags. switch (event.keyCode) { case 0x1000001: // Up arrow. sendString(isApplicationKeypad ? "\u001bOA" : "\u001b[A"); //$NON-NLS-1$ //$NON-NLS-2$ break; case 0x1000002: // Down arrow. sendString(isApplicationKeypad ? "\u001bOB" : "\u001b[B"); //$NON-NLS-1$ //$NON-NLS-2$ break; case 0x1000003: // Left arrow. sendString(isApplicationKeypad ? "\u001bOD" : "\u001b[D"); //$NON-NLS-1$ //$NON-NLS-2$ break; case 0x1000004: // Right arrow. sendString(isApplicationKeypad ? "\u001bOC" : "\u001b[C"); //$NON-NLS-1$ //$NON-NLS-2$ break; case 0x1000005: // PgUp key. sendString("\u001b[5~"); //$NON-NLS-1$ break; case 0x1000006: // PgDn key. sendString("\u001b[6~"); //$NON-NLS-1$ break; case 0x1000007: // Home key. sendString(isApplicationKeypad ? "\u001bOH" : "\u001b[H"); //$NON-NLS-1$ //$NON-NLS-2$ break; case 0x1000008: // End key. sendString(isApplicationKeypad ? "\u001bOF" : "\u001b[F"); //$NON-NLS-1$ //$NON-NLS-2$ break; case 0x100000a: // F1 key. if ((event.stateMask & SWT.CTRL) != 0) { //Allow Ctrl+F1 to act locally as well as on the remote, because it is //typically non-intrusive event.doit = true; } sendString("\u001b[M"); //$NON-NLS-1$ break; case 0x100000b: // F2 key. sendString("\u001b[N"); //$NON-NLS-1$ break; case 0x100000c: // F3 key. sendString("\u001b[O"); //$NON-NLS-1$ break; case 0x100000d: // F4 key. sendString("\u001b[P"); //$NON-NLS-1$ break; case 0x100000e: // F5 key. sendString("\u001b[Q"); //$NON-NLS-1$ break; case 0x100000f: // F6 key. sendString("\u001b[R"); //$NON-NLS-1$ break; case 0x1000010: // F7 key. sendString("\u001b[S"); //$NON-NLS-1$ break; case 0x1000011: // F8 key. sendString("\u001b[T"); //$NON-NLS-1$ break; case 0x1000012: // F9 key. sendString("\u001b[U"); //$NON-NLS-1$ break; case 0x1000013: // F10 key. sendString("\u001b[V"); //$NON-NLS-1$ break; case 0x1000014: // F11 key. sendString("\u001b[W"); //$NON-NLS-1$ break; case 0x1000015: // F12 key. sendString("\u001b[X"); //$NON-NLS-1$ break; default: // Ignore other special keys. Control flows through this case when // the user presses SHIFT, CONTROL, ALT, and any other key not // handled by the above cases. break; } // It's ok to return here, because we never locally echo special keys. return; } else if (event.stateMask == 0) { switch (event.keyCode) { case 8: sendString("\u007f"); //$NON-NLS-1$ return; case 127: sendString("\u001b[3~"); //$NON-NLS-1$ return; default: break; } } // To fix SPR 110341, we consider the Alt key to be pressed only when the // Control key is _not_ also pressed. This works around a bug in SWT where, // on European keyboards, the AltGr key being pressed appears to us as Control // + Alt being pressed simultaneously. Logger.log("stateMask = " + event.stateMask); //$NON-NLS-1$ boolean altKeyPressed = (((event.stateMask & SWT.ALT) != 0) && ((event.stateMask & SWT.CTRL) == 0)); if (!altKeyPressed && (event.stateMask & SWT.CTRL) != 0 && character == ' ') { // Send a NUL character -- many terminal emulators send NUL when // Control-Space is pressed. This is used to set the mark in Emacs. character = '\u0000'; } sendChar(character, altKeyPressed); // Special case: When we are in a TCP connection and echoing characters // locally, send a LF after sending a CR. // ISSUE: Is this absolutely required? if (character == '\r' && getTerminalConnector() != null && isConnected() && getTerminalConnector().isLocalEcho()) { sendChar('\n', false); } // Now decide if we should locally echo the character we just sent. We do // _not_ locally echo the character if any of these conditions are true: // // o This is a serial connection. // // o This is a TCP connection (i.e., m_telnetConnection is not null) and // the remote endpoint is not a TELNET server. // // o The ALT (or META) key is pressed. // // o The character is any of the first 32 ISO Latin-1 characters except // Control-I or Control-M. // // o The character is the DELETE character. if (getTerminalConnector() == null || getTerminalConnector().isLocalEcho() == false || altKeyPressed || (character >= '\u0001' && character < '\t') || (character > '\t' && character < '\r') || (character > '\r' && character <= '\u001f') || character == '\u007f') { // No local echoing. return; } // Locally echo the character. StringBuffer charBuffer = new StringBuffer(); charBuffer.append(character); // If the character is a carriage return, we locally echo it as a CR + LF // combination. if (character == '\r') charBuffer.append('\n'); writeToTerminal(charBuffer.toString()); } } public void setTerminalTitle(String title) { fTerminalListener.setTerminalTitle(title); } public TerminalState getState() { return fState; } public void setState(TerminalState state) { fState = state; fTerminalListener.setState(state); // enable the (blinking) cursor if the terminal is connected runAsyncInDisplayThread(new Runnable() { public void run() { if (fCtlText != null && !fCtlText.isDisposed()) fCtlText.setCursorEnabled(isConnected()); } }); } /** * @param runnable run in display thread */ private void runAsyncInDisplayThread(Runnable runnable) { if (Display.findDisplay(Thread.currentThread()) != null) runnable.run(); else if (PlatformUI.isWorkbenchRunning()) PlatformUI.getWorkbench().getDisplay().asyncExec(runnable); // else should not happen and we ignore it... } public String getSettingsSummary() { if (getTerminalConnector() != null) return getTerminalConnector().getSettingsSummary(); return ""; //$NON-NLS-1$ } public void setConnector(ITerminalConnector connector) { fConnector = connector; } public ICommandInputField getCommandInputField() { return fCommandInputField; } public void setCommandInputField(ICommandInputField inputField) { if (fCommandInputField != null) fCommandInputField.dispose(); fCommandInputField = inputField; if (fCommandInputField != null) fCommandInputField.createControl(fWndParent, this); if (fWndParent.isVisible()) fWndParent.layout(true); } public int getBufferLineLimit() { return fTerminalModel.getMaxHeight(); } public void setBufferLineLimit(int bufferLineLimit) { if (bufferLineLimit <= 0) return; synchronized (fTerminalModel) { if (fTerminalModel.getHeight() > bufferLineLimit) fTerminalModel.setDimensions(bufferLineLimit, fTerminalModel.getWidth()); fTerminalModel.setMaxHeight(bufferLineLimit); } } public boolean isScrollLock() { return fCtlText.isScrollLock(); } public void setScrollLock(boolean on) { fCtlText.setScrollLock(on); } public void setInvertedColors(boolean invert) { fCtlText.setInvertedColors(invert); } public void setApplicationKeypad(boolean mode) { isApplicationKeypad = mode; } }