| /******************************************************************************* |
| * 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 |
| *******************************************************************************/ |
| package com.google.eclipse.elt.emulator.core; |
| |
| import static com.google.eclipse.elt.emulator.impl.TerminalPlugin.isOptionEnabled; |
| import static org.eclipse.core.runtime.Status.OK_STATUS; |
| import static org.eclipse.jface.bindings.keys.SWTKeySupport.convertEventToUnmodifiedAccelerator; |
| |
| 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.ui.PlatformUI; |
| import org.eclipse.ui.contexts.*; |
| import org.eclipse.ui.keys.IBindingService; |
| |
| import com.google.eclipse.elt.emulator.control.*; |
| import com.google.eclipse.elt.emulator.impl.*; |
| import com.google.eclipse.elt.emulator.model.*; |
| import com.google.eclipse.elt.emulator.provisional.api.*; |
| import com.google.eclipse.elt.emulator.textcanvas.*; |
| import com.google.eclipse.elt.emulator.textcanvas.PipedInputStream; |
| |
| public class VT100TerminalControl implements ITerminalControlForText, ITerminalControl, ITerminalViewControl { |
| protected static final String[] LINE_DELIMITERS = { "\n" }; |
| |
| private static final String DEFAULT_ENCODING = |
| new InputStreamReader(new ByteArrayInputStream(new byte[0])).getEncoding(); |
| |
| // 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 terminalText; |
| |
| private Display display; |
| private TextCanvas textControl; |
| private Composite rootControl; |
| private Clipboard clipboard; |
| private KeyListener keyHandler; |
| private final ITerminalListener terminalListener; |
| private String message = ""; |
| private FocusListener focusListener; |
| private ITerminalConnector connector; |
| private final ITerminalConnector[] connectors; |
| private final PipedInputStream inputStream; |
| private String encoding = DEFAULT_ENCODING; |
| private InputStreamReader inputStreamReader; |
| private ICommandInputField commandInputField; |
| private volatile TerminalState state; |
| private final ITerminalTextData terminalModel; |
| |
| volatile private Job job; |
| |
| private final EditActionAccelerators editActionAccelerators = new EditActionAccelerators(); |
| |
| public VT100TerminalControl(ITerminalListener target, Composite wndParent, ITerminalConnector[] connectors) { |
| this.connectors = connectors; |
| terminalListener = target; |
| terminalModel = TerminalTextDataFactory.makeTerminalTextData(); |
| terminalModel.setMaxHeight(1000); |
| inputStream = new PipedInputStream(8 * 1024); |
| terminalText = new VT100Emulator(terminalModel, this, null); |
| try { |
| // Use default Encoding as start, until setEncoding() is called. |
| setEncoding(null); |
| } catch (UnsupportedEncodingException e) { |
| e.printStackTrace(); |
| // Fall back to local platform default encoding |
| encoding = DEFAULT_ENCODING; |
| inputStreamReader = new InputStreamReader(inputStream); |
| terminalText.setInputStreamReader(inputStreamReader); |
| } |
| setUpTerminal(wndParent); |
| } |
| |
| @Override public void setEncoding(String encoding) throws UnsupportedEncodingException { |
| if (encoding == null) { |
| encoding = "ISO-8859-1"; |
| } |
| inputStreamReader = new InputStreamReader(inputStream, encoding); |
| // remember encoding if above didn't throw an exception |
| this.encoding = encoding; |
| terminalText.setInputStreamReader(inputStreamReader); |
| } |
| |
| @Override public String getEncoding() { |
| return encoding; |
| } |
| |
| @Override public ITerminalConnector[] getConnectors() { |
| return connectors; |
| } |
| |
| @Override public void copy() { |
| copy(DND.CLIPBOARD); |
| } |
| |
| private void copy(int clipboardType) { |
| String selection = getSelection(); |
| if (selection == null || selection.isEmpty()) { |
| return; |
| } |
| Object[] data = new Object[] { selection }; |
| Transfer[] types = new Transfer[] { TextTransfer.getInstance() }; |
| clipboard.setContents(data, types, clipboardType); |
| } |
| |
| @Override public void paste() { |
| paste(DND.CLIPBOARD); |
| } |
| |
| private void paste(int clipboardType) { |
| TextTransfer textTransfer = TextTransfer.getInstance(); |
| String strText = (String) clipboard.getContents(textTransfer, clipboardType); |
| pasteString(strText); |
| } |
| |
| @Override public boolean pasteString(String text) { |
| if (!isConnected()) { |
| return false; |
| } |
| if (text == null) { |
| return false; |
| } |
| if (!encoding.equals(DEFAULT_ENCODING)) { |
| sendString(text); |
| } else { |
| // TODO I do not understand why pasteString would do this here... |
| for (int i = 0; i < text.length(); i++) { |
| sendChar(text.charAt(i), false); |
| } |
| } |
| return true; |
| } |
| |
| @Override public void selectAll() { |
| getTextControl().selectAll(); |
| } |
| |
| @Override public void sendKey(char character) { |
| Event event; |
| KeyEvent keyEvent; |
| event = new Event(); |
| event.widget = getTextControl(); |
| event.character = character; |
| event.keyCode = 0; |
| event.stateMask = 0; |
| event.doit = true; |
| keyEvent = new KeyEvent(event); |
| keyHandler.keyPressed(keyEvent); |
| } |
| |
| @Override public void clearTerminal() { |
| getTerminalText().clearTerminal(); |
| } |
| |
| @Override public Clipboard getClipboard() { |
| return clipboard; |
| } |
| |
| @Override public String getSelection() { |
| String text = textControl.getSelectionText(); |
| return text == null ? "" : text; |
| } |
| |
| @Override public boolean setFocus() { |
| return getTextControl().setFocus(); |
| } |
| |
| @Override public boolean isEmpty() { |
| return getTextControl().isEmpty(); |
| } |
| |
| @Override public boolean isDisposed() { |
| return getTextControl().isDisposed(); |
| } |
| |
| @Override public boolean isConnected() { |
| return state == TerminalState.CONNECTED; |
| } |
| |
| @Override public void disposeTerminal() { |
| disconnectTerminal(); |
| clipboard.dispose(); |
| getTerminalText().dispose(); |
| } |
| |
| @Override public void connectTerminal() { |
| if (getTerminalConnector() == null) { |
| return; |
| } |
| terminalText.resetState(); |
| if (connector.getInitializationErrorMessage() != null) { |
| showErrorMessage(NLS.bind(TerminalMessages.cannotConnectTo, connector.getName(), |
| connector.getInitializationErrorMessage())); |
| return; |
| } |
| getTerminalConnector().connect(this); |
| // clean the error message |
| setErrorMessage(""); |
| waitForConnect(); |
| } |
| |
| @Override public ITerminalConnector getTerminalConnector() { |
| return connector; |
| } |
| |
| @Override public void disconnectTerminal() { |
| Logger.log("entered."); |
| // Disconnect the remote side first. |
| if (getState() != TerminalState.CLOSED && getTerminalConnector() != null) { |
| getTerminalConnector().disconnect(); |
| } |
| // Ensure that a new Job can be started; then clean up old Job. |
| Job newJob; |
| synchronized (this) { |
| newJob = job; |
| job = null; |
| } |
| if (newJob != null) { |
| newJob.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 = newJob.getThread(); |
| if (t != null) { |
| t.interrupt(); |
| } |
| try { |
| newJob.join(); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| } |
| |
| private void waitForConnect() { |
| // TODO |
| // Eliminate this code |
| while (getState() == TerminalState.CONNECTING) { |
| if (display.readAndDispatch()) { |
| continue; |
| } |
| display.sleep(); |
| } |
| if (getTextControl().isDisposed()) { |
| disconnectTerminal(); |
| return; |
| } |
| if (!getMsg().isEmpty()) { |
| showErrorMessage(getMsg()); |
| disconnectTerminal(); |
| return; |
| } |
| getTextControl().setFocus(); |
| startReaderJob(); |
| } |
| |
| private synchronized void startReaderJob() { |
| if (job == null) { |
| initializeJob(); |
| job.setSystem(true); |
| job.schedule(); |
| } |
| } |
| |
| private void initializeJob() { |
| job = new Job("Terminal data reader") { |
| @Override protected IStatus run(IProgressMonitor monitor) { |
| IStatus status = OK_STATUS; |
| try { |
| while (true) { |
| while (inputStream.available() == 0 && !monitor.isCanceled()) { |
| try { |
| inputStream.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! |
| terminalText.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 (job == this) { |
| job = null; |
| } |
| } |
| } |
| return status; |
| } |
| }; |
| } |
| |
| private void showErrorMessage(String message) { |
| String title = TerminalMessages.terminalError; |
| MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK); |
| mb.setText(title); |
| 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(encoding)); |
| getOutputStream().flush(); |
| } catch (SocketException socketException) { |
| displayTextInTerminal(socketException.getMessage()); |
| String strMsg = TerminalMessages.socketError + "!\n" + socketException.getMessage(); |
| showErrorMessage(strMsg); |
| Logger.logException(socketException); |
| disconnectTerminal(); |
| } catch (IOException ioException) { |
| showErrorMessage(TerminalMessages.ioError + "!\n" + ioException.getMessage()); |
| Logger.logException(ioException); |
| disconnectTerminal(); |
| } |
| } |
| |
| @Override public Shell getShell() { |
| return getTextControl().getShell(); |
| } |
| |
| protected void sendChar(char chKey, boolean altKeyPressed) { |
| try { |
| String text = Character.toString(chKey); |
| byte[] bytes = text.getBytes(getEncoding()); |
| OutputStream os = getOutputStream(); |
| if (os == null) { |
| // Bug 207785: NPE when trying to send char while no longer connected |
| Logger.log("NOT sending '" + text + "' because no longer connected"); |
| } 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 + '" + text + "'"); |
| getOutputStream().write('\u001b'); |
| getOutputStream().write(bytes); |
| } else { |
| Logger.log("sending '" + text + "'"); |
| getOutputStream().write(bytes); |
| } |
| getOutputStream().flush(); |
| } |
| } catch (SocketException socketException) { |
| Logger.logException(socketException); |
| displayTextInTerminal(socketException.getMessage()); |
| String message = TerminalMessages.socketError + "!\n" + socketException.getMessage(); |
| showErrorMessage(message); |
| Logger.logException(socketException); |
| disconnectTerminal(); |
| } catch (IOException ioException) { |
| Logger.logException(ioException); |
| displayTextInTerminal(ioException.getMessage()); |
| String message = TerminalMessages.ioError + "!\n" + ioException.getMessage(); |
| showErrorMessage(message); |
| Logger.logException(ioException); |
| disconnectTerminal(); |
| } |
| } |
| |
| @Override public void setUpTerminal(Composite parent) { |
| Assert.isNotNull(parent); |
| state = TerminalState.CLOSED; |
| setUpControls(parent); |
| setupListeners(); |
| } |
| |
| @Override public void setFont(Font font) { |
| getTextControl().setFont(font); |
| if (commandInputField != null) { |
| commandInputField.setFont(font); |
| } |
| // Tell the TerminalControl singleton that the font has changed. |
| textControl.onFontChange(); |
| getTerminalText().fontChanged(); |
| } |
| |
| @Override public Font getFont() { |
| return getTextControl().getFont(); |
| } |
| |
| @Override public Control getControl() { |
| return textControl; |
| } |
| |
| @Override public Control getRootControl() { |
| return rootControl; |
| } |
| |
| 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(). |
| rootControl = new Composite(parent, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| layout.verticalSpacing = 0; |
| rootControl.setLayout(layout); |
| ITerminalTextDataSnapshot snapshot = terminalModel.makeSnapshot(); |
| // TODO how to get the initial size correctly! |
| snapshot.updateSnapshot(false); |
| ITextCanvasModel canvasModel = new PollingTextCanvasModel(snapshot); |
| textControl = new TextCanvas(rootControl, canvasModel, SWT.NONE, new TextLineRenderer(textControl, canvasModel)); |
| textControl.addMouseListener(new MouseAdapter() { |
| @Override public void mouseUp(MouseEvent e) { |
| IHyperlink hyperlink = hyperlinkAt(e); |
| if (hyperlink != null) { |
| hyperlink.open(); |
| } |
| } |
| }); |
| textControl.addMouseMoveListener(new MouseMoveListener() { |
| @Override public void mouseMove(MouseEvent e) { |
| IHyperlink hyperlink = hyperlinkAt(e); |
| int cursorId = (hyperlink == null) ? SWT.CURSOR_IBEAM : SWT.CURSOR_HAND; |
| Cursor newCursor = textControl.getDisplay().getSystemCursor(cursorId); |
| if (!newCursor.equals(textControl.getCursor())) { |
| textControl.setCursor(newCursor); |
| } |
| } |
| }); |
| textControl.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| textControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| textControl.addResizeHandler(new TextCanvas.ResizeListener() { |
| @Override public void sizeChanged(int lines, int columns) { |
| terminalText.setDimensions(lines, columns); |
| } |
| }); |
| textControl.addMouseListener(new MouseAdapter() { |
| @Override public void mouseUp(MouseEvent e) { |
| // update selection used by middle mouse button paste |
| if (e.button == 1 && getSelection().length() > 0) { |
| copy(DND.SELECTION_CLIPBOARD); |
| } |
| } |
| }); |
| display = getTextControl().getDisplay(); |
| clipboard = new Clipboard(display); |
| setFont(JFaceResources.getTextFont()); |
| } |
| |
| private IHyperlink hyperlinkAt(MouseEvent e) { |
| Point p = textControl.screenPointToCell(e.x, e.y); |
| if (p != null) { |
| List<IHyperlink> hyperlinks = terminalText.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) { |
| return hyperlink; |
| } |
| } |
| } |
| return null; |
| } |
| |
| protected void setupListeners() { |
| keyHandler = new TerminalKeyHandler(); |
| focusListener = new TerminalFocusListener(); |
| getTextControl().addKeyListener(keyHandler); |
| getTextControl().addFocusListener(focusListener); |
| } |
| |
| @Override public void displayTextInTerminal(String text) { |
| writeToTerminal("\r\n" + text + "\r\n"); |
| } |
| |
| private void writeToTerminal(String text) { |
| try { |
| getRemoteToTerminalOutputStream().write(text.getBytes(encoding)); |
| } catch (UnsupportedEncodingException e) { |
| // should never happen! |
| e.printStackTrace(); |
| } catch (IOException e) { |
| // should never happen! |
| e.printStackTrace(); |
| } |
| } |
| |
| @Override public OutputStream getRemoteToTerminalOutputStream() { |
| if (Logger.isLogEnabled()) { |
| return new LoggingOutputStream(inputStream.getOutputStream()); |
| } |
| return inputStream.getOutputStream(); |
| } |
| |
| protected boolean isLogCharEnabled() { |
| return isOptionEnabled(Logger.TRACE_DEBUG_LOG_CHAR); |
| } |
| |
| protected boolean isLogBufferSizeEnabled() { |
| return isOptionEnabled(Logger.TRACE_DEBUG_LOG_BUFFER_SIZE); |
| } |
| |
| @Override public OutputStream getOutputStream() { |
| if (getTerminalConnector() != null) { |
| return getTerminalConnector().getTerminalToRemoteStream(); |
| } |
| return null; |
| } |
| |
| @Override public void setErrorMessage(String message) { |
| this.message = message; |
| } |
| |
| public String getMsg() { |
| return message; |
| } |
| |
| protected TextCanvas getTextControl() { |
| return textControl; |
| } |
| |
| public VT100Emulator getTerminalText() { |
| return terminalText; |
| } |
| |
| protected class TerminalFocusListener implements FocusListener { |
| private IContextActivation contextActivation = null; |
| |
| @Override public void focusGained(FocusEvent event) { |
| // Disable all keyboard accelerators (e.g., Control-B) so the Terminal view can see every key stroke. 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.google.eclipse.elt.emulator.TerminalContext"); |
| } |
| |
| @Override public void focusLost(FocusEvent event) { |
| // Enable all key bindings. |
| 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 { |
| @Override public void keyPressed(KeyEvent event) { |
| if (getState() == TerminalState.CONNECTING) { |
| return; |
| } |
| int accelerator = convertEventToUnmodifiedAccelerator(event); |
| if (editActionAccelerators.isCopyAction(accelerator)) { |
| copy(); |
| return; |
| } |
| if (editActionAccelerators.isPasteAction(accelerator)) { |
| paste(); |
| 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 (state == 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~"); |
| 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"); |
| break; |
| case 0x1000002: // Down arrow. |
| sendString("\u001b[B"); |
| break; |
| case 0x1000003: // Left arrow. |
| sendString("\u001b[D"); |
| break; |
| case 0x1000004: // Right arrow. |
| sendString("\u001b[C"); |
| break; |
| case 0x1000005: // PgUp key. |
| sendString("\u001b[5~"); |
| break; |
| case 0x1000006: // PgDn key. |
| sendString("\u001b[6~"); |
| break; |
| case 0x1000007: // Home key. |
| sendString("\u001b[H"); |
| break; |
| case 0x1000008: // End key. |
| sendString("\u001b[F"); |
| break; |
| case 0x1000009: // Insert. |
| sendString("\u001b[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"); |
| break; |
| case 0x100000b: // F2 key. |
| sendString("\u001b[N"); |
| break; |
| case 0x100000c: // F3 key. |
| sendString("\u001b[O"); |
| break; |
| case 0x100000d: // F4 key. |
| sendString("\u001b[P"); |
| break; |
| case 0x100000e: // F5 key. |
| sendString("\u001b[Q"); |
| break; |
| case 0x100000f: // F6 key. |
| sendString("\u001b[R"); |
| break; |
| case 0x1000010: // F7 key. |
| sendString("\u001b[S"); |
| break; |
| case 0x1000011: // F8 key. |
| sendString("\u001b[T"); |
| break; |
| case 0x1000012: // F9 key. |
| sendString("\u001b[U"); |
| break; |
| case 0x1000013: // F10 key. |
| sendString("\u001b[V"); |
| break; |
| case 0x1000014: // F11 key. |
| sendString("\u001b[W"); |
| break; |
| case 0x1000015: // F12 key. |
| sendString("\u001b[X"); |
| 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); |
| 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: |
| // |
| // * This is a serial connection. |
| // * This is a TCP connection (i.e., m_telnetConnection is not null) and the remote endpoint is not a TELNET |
| // server. |
| // * The ALT (or META) key is pressed. |
| // * The character is any of the first 32 ISO Latin-1 characters except Control-I or Control-M. |
| // * 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. |
| StringBuilder charBuffer = new StringBuilder(); |
| 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()); |
| } |
| } |
| |
| @Override public void setTerminalTitle(String title) { |
| terminalListener.setTerminalTitle(title); |
| } |
| |
| @Override public TerminalState getState() { |
| return state; |
| } |
| |
| @Override public void setState(TerminalState state) { |
| this.state = state; |
| terminalListener.setState(state); |
| // enable the (blinking) cursor if the terminal is connected |
| runAsyncInDisplayThread(new Runnable() { |
| @Override public void run() { |
| if (textControl != null && !textControl.isDisposed()) { |
| textControl.setCursorEnabled(isConnected()); |
| } |
| } |
| }); |
| } |
| |
| 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... |
| } |
| } |
| |
| @Override public String getSettingsSummary() { |
| if (getTerminalConnector() != null) { |
| return getTerminalConnector().getSettingsSummary(); |
| } |
| return ""; |
| } |
| |
| @Override public void setConnector(ITerminalConnector connector) { |
| this.connector = connector; |
| } |
| |
| @Override public ICommandInputField getCommandInputField() { |
| return commandInputField; |
| } |
| |
| @Override public void setCommandInputField(ICommandInputField inputField) { |
| if (commandInputField != null) { |
| commandInputField.dispose(); |
| } |
| commandInputField = inputField; |
| if (commandInputField != null) { |
| commandInputField.createControl(rootControl, this); |
| } |
| if (rootControl.isVisible()) { |
| rootControl.layout(true); |
| } |
| } |
| |
| @Override public int getBufferLineLimit() { |
| return terminalModel.getMaxHeight(); |
| } |
| |
| @Override public void setBufferLineLimit(int bufferLineLimit) { |
| if (bufferLineLimit <= 0) { |
| return; |
| } |
| synchronized (terminalModel) { |
| if (terminalModel.getHeight() > bufferLineLimit) { |
| terminalModel.setDimensions(bufferLineLimit, terminalModel.getWidth()); |
| } |
| terminalModel.setMaxHeight(bufferLineLimit); |
| } |
| } |
| |
| @Override public boolean isScrollLockOn() { |
| return textControl.isScrollLockOn(); |
| } |
| |
| @Override public void setScrollLockOn(boolean on) { |
| textControl.setScrollLockOn(on); |
| } |
| |
| @Override public void setInvertedColors(boolean invert) { |
| textControl.setInvertedColors(invert); |
| } |
| |
| @Override |
| public void setColors(RGB background, RGB foreground) { |
| textControl.setColors(background, foreground); |
| } |
| |
| @Override |
| public void setBlinkingCursor(boolean useBlinkingCursor) { |
| textControl.setBlinkingCursor(useBlinkingCursor); |
| } |
| } |