| /******************************************************************************* |
| * 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 |
| * Pawel Piech (Wind River) - [333613] "Job found still running" after shutdown |
| * Martin Oberhuber (Wind River) - [348700] Terminal unusable after disconnect |
| * Simon Bernard (Sierra Wireless) - [351424] [terminal] Terminal does not support del and insert key |
| *******************************************************************************/ |
| package org.eclipse.tm.internal.terminal.emulator; |
| |
| import java.io.*; |
| import java.net.SocketException; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.hyperlink.IHyperlink; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.dnd.*; |
| import org.eclipse.swt.events.*; |
| import org.eclipse.swt.graphics.*; |
| import org.eclipse.swt.layout.*; |
| import org.eclipse.swt.widgets.*; |
| import org.eclipse.tm.internal.terminal.control.*; |
| import org.eclipse.tm.internal.terminal.control.impl.*; |
| import org.eclipse.tm.internal.terminal.provisional.api.*; |
| import org.eclipse.tm.internal.terminal.textcanvas.*; |
| import org.eclipse.tm.internal.terminal.textcanvas.PipedInputStream; |
| import org.eclipse.tm.terminal.model.*; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.contexts.*; |
| 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$ |
| |
| /** |
| * 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 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$ |
| |
| //Disconnect the remote side first |
| if (getState()!=TerminalState.CLOSED) { |
| if(getTerminalConnector()!=null) { |
| getTerminalConnector().disconnect(); |
| } |
| } |
| |
| //Ensure that a new Job can be started; then clean up old Job. |
| Job job; |
| synchronized(this) { |
| job = fJob; |
| fJob = null; |
| } |
| if (job!=null) { |
| job.cancel(); |
| // Join job to avoid leaving job running after workbench shutdown (333613). |
| // Interrupt to be fast enough; cannot close fInputStream since it is re-used (bug 348700). |
| Thread t = job.getThread(); |
| if(t!=null) { |
| t.interrupt(); |
| } |
| try { |
| job.join(); |
| } catch (InterruptedException e) {} |
| } |
| } |
| |
| // 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 { |
| 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(org.eclipse.swt.widgets.Composite) |
| */ |
| public void setupTerminal(Composite parent) { |
| Assert.isNotNull(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=new TextCanvas(fWndParent,canvasModel,SWT.NONE,new TextLineRenderer(fCtlText,canvasModel)); |
| fCtlText.addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseUp(MouseEvent e) { |
| Point p = fCtlText.screenPointToCell(e.x, e.y); |
| if (p == null) { |
| return; |
| } |
| List<IHyperlink> hyperlinks = fTerminalText.hyperlinksAt(p.y); |
| for (IHyperlink hyperlink : hyperlinks) { |
| IRegion region = hyperlink.getHyperlinkRegion(); |
| int start = region.getOffset(); |
| int end = start + region.getLength() - 1; |
| if (p.x >= start && p.x <= end) { |
| hyperlink.open(); |
| } |
| } |
| } |
| }); |
| |
| fCtlText.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| fCtlText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| 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 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("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; |
| } |
| |
| // 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; |
| |
| char character = event.character; |
| |
| //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; |
| } |
| |
| // Manage the Del key |
| if (event.keyCode == 0x000007f) |
| { |
| sendString("\u001b[3~"); //$NON-NLS-1$ |
| 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("\u001b[A"); //$NON-NLS-1$ |
| break; |
| |
| case 0x1000002: // Down arrow. |
| sendString("\u001b[B"); //$NON-NLS-1$ |
| break; |
| |
| case 0x1000003: // Left arrow. |
| sendString("\u001b[D"); //$NON-NLS-1$ |
| break; |
| |
| case 0x1000004: // Right arrow. |
| sendString("\u001b[C"); //$NON-NLS-1$ |
| 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("\u001b[H"); //$NON-NLS-1$ |
| break; |
| |
| case 0x1000008: // End key. |
| sendString("\u001b[F"); //$NON-NLS-1$ |
| break; |
| |
| case 0x1000009: // Insert. |
| sendString("\u001b[2~"); //$NON-NLS-1$ |
| 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; |
| } |
| |
| // 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 setColors(RGB background, RGB foreground) { |
| fCtlText.setColors(background, foreground); |
| } |
| } |