/******************************************************************************* * Copyright (c) 2007, 2014 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 * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.internal.debug.ui.model; import java.io.IOException; import java.io.OutputStream; import java.util.LinkedList; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.tcf.internal.debug.model.TCFLaunch; import org.eclipse.tcf.internal.debug.ui.Activator; import org.eclipse.tcf.internal.debug.ui.ImageCache; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IErrorReport; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.services.IProcessesV1; import org.eclipse.tcf.services.IRunControl; import org.eclipse.tcf.util.TCFDataCache; import org.eclipse.tm.internal.terminal.control.ITerminalListener; import org.eclipse.tm.internal.terminal.control.ITerminalViewControl; import org.eclipse.tm.internal.terminal.control.TerminalViewControlFactory; import org.eclipse.tm.internal.terminal.provisional.api.ISettingsPage; import org.eclipse.tm.internal.terminal.provisional.api.ISettingsStore; 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.TerminalState; import org.eclipse.ui.IActionBars; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.AbstractConsole; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleManager; import org.eclipse.ui.console.IConsoleView; import org.eclipse.ui.part.IPageBookViewPage; import org.eclipse.ui.part.Page; @SuppressWarnings("restriction") class TCFConsole extends AbstractConsole { public static final int TYPE_PROCESS_CONSOLE = 1, TYPE_PROCESS_TERMINAL = 2, TYPE_UART_TERMINAL = 3, TYPE_CMD_LINE = 4, TYPE_DPRINTF = 5; private static int page_id_cnt = 0; private final TCFModel model; private final Display display; private final String ctx_id; private final int type; private final LinkedList<ViewPage> pages = new LinkedList<ViewPage>(); private final LinkedList<Message> history = new LinkedList<Message>(); private static class Message { int stream_id; byte[] data; } private class ViewPage extends Page implements ITerminalConnector, ITerminalListener { private final String page_id = "Page-" + page_id_cnt++; private final LinkedList<Message> inp_queue = new LinkedList<Message>(); private final OutputStream out_stream = new OutputStream() { @Override public void write(final int b) throws IOException { if (ctx_id == null) return; Protocol.invokeAndWait(new Runnable() { public void run() { try { String s = "" + (char)b; byte[] buf = s.getBytes("UTF8"); model.getLaunch().writeProcessInputStream(ctx_id, buf, 0, buf.length); } catch (Exception x) { model.onProcessStreamError(ctx_id, 0, x, 0); } } }); } }; private ITerminalViewControl view_control; private OutputStream rtt; private int ws_col; private int ws_row; private final Thread inp_thread = new Thread() { public void run() { try { for (;;) { Message m = null; synchronized (inp_queue) { while (inp_queue.size() == 0) inp_queue.wait(); m = inp_queue.removeFirst(); } if (m.data == null) break; if (type == TYPE_PROCESS_CONSOLE) { String s = "\u001b[30m"; switch (m.stream_id) { case 1: s = "\u001b[31m"; break; case 2: s = "\u001b[34m"; break; case 3: s = "\u001b[32m"; break; } rtt.write(s.getBytes("UTF8")); for (int i = 0; i < m.data.length; i++) { int ch = m.data[i] & 0xff; if (ch == '\n') rtt.write('\r'); rtt.write(ch); } } else if (type == TYPE_DPRINTF) { int i = 0; for (int j = 0; j < m.data.length; j++) { if (m.data[j] == '\n') { rtt.write(m.data, i, j - i); rtt.write('\r'); i = j; } } rtt.write(m.data, i, m.data.length - i); } else { rtt.write(m.data); } } } catch (Throwable x) { Activator.log("Cannot write console output", x); } } }; @Override public void createControl(Composite parent) { assert view_control == null; view_control = TerminalViewControlFactory.makeControl(this, parent, null); view_control.setConnector(this); view_control.connectTerminal(); } @Override public void dispose() { if (view_control != null) { // TODO: need a way to stop terminal update timer, see PollingTextCanvasModel // It the timer is not stopped, it causes memory leak view_control.disposeTerminal(); view_control.setConnector(null); view_control = null; } } @Override public Control getControl() { return view_control.getRootControl(); } @Override public void setActionBars(IActionBars actionBars) { } @Override public void setFocus() { view_control.setFocus(); } @Override @SuppressWarnings("rawtypes") public Object getAdapter(Class adapter) { return null; } @Override public void setTerminalSize(final int w, final int h) { if (ws_col == w && ws_row == h) return; ws_col = w; ws_row = h; if (type != TYPE_PROCESS_TERMINAL) return; Protocol.invokeLater(new Runnable() { @Override public void run() { final TCFLaunch launch = model.getLaunch(); if (launch.isProcessExited()) return; final IChannel channel = launch.getChannel(); if (channel.getState() != IChannel.STATE_OPEN) return; IProcessesV1 prs = channel.getRemoteService(IProcessesV1.class); if (prs == null) return; prs.setWinSize(ctx_id, w, h, new IProcessesV1.DoneCommand() { @Override public void doneCommand(IToken token, Exception error) { if (error == null) return; if (launch.isProcessExited()) return; if (channel.getState() != IChannel.STATE_OPEN) return; if (error instanceof IErrorReport && ((IErrorReport)error).getErrorCode() == IErrorReport.TCF_ERROR_INV_COMMAND) return; Activator.log("Cannot set process TTY window size", error); } }); } }); } @Override public void save(ISettingsStore store) { } public void setDefaultSettings() { } @Override public void load(ISettingsStore store) { } @Override public boolean isLocalEcho() { return false; } @Override public boolean isInitialized() { return true; } @Override public boolean isHidden() { return false; } @Override public OutputStream getTerminalToRemoteStream() { return out_stream; } @Override public String getSettingsSummary() { return null; } @Override public String getName() { return TCFConsole.this.getName(); } @Override public String getInitializationErrorMessage() { return null; } @Override public String getId() { return page_id; } @Override public void disconnect() { Protocol.invokeAndWait(new Runnable() { @Override public void run() { pages.remove(ViewPage.this); synchronized (inp_queue) { inp_queue.add(new Message()); inp_queue.notify(); } } }); } @Override public void connect(ITerminalControl term_control) { try { term_control.setState(TerminalState.CONNECTING); term_control.setEncoding("UTF8"); rtt = term_control.getRemoteToTerminalOutputStream(); Protocol.invokeAndWait(new Runnable() { @Override public void run() { pages.add(ViewPage.this); for (Message m : history) inp_queue.add(m); inp_thread.setName("TCF Console Input"); inp_thread.start(); } }); term_control.setState(TerminalState.CONNECTED); } catch (Exception x) { Activator.log("Cannot connect a terminal", x); term_control.setState(TerminalState.CLOSED); } } @Override public void setState(TerminalState state) { } @Override public void setTerminalTitle(String title) { } public ISettingsPage makeSettingsPage() { return null; } } TCFConsole(final TCFModel model, int type, String ctx_id) { super(getViewName(type, ctx_id), null, getImageDescriptor(ctx_id), false); this.model = model; this.type = type; this.ctx_id = ctx_id; display = model.getDisplay(); model.asyncExec(new Runnable() { public void run() { try { if (PlatformUI.getWorkbench().isClosing()) { return; } if (!PlatformUI.isWorkbenchRunning() || PlatformUI.getWorkbench().isStarting()) { display.timerExec(200, this); return; } IConsoleManager manager = ConsolePlugin.getDefault().getConsoleManager(); manager.addConsoles(new IConsole[]{ TCFConsole.this }); manager.showConsoleView(TCFConsole.this); } catch (Throwable x) { Activator.log("Cannot open Console view", x); } } }); } private static ImageDescriptor getImageDescriptor(String ctx_id) { String image = ctx_id != null ? ImageCache.IMG_PROCESS_RUNNING : ImageCache.IMG_TCF; return ImageCache.getImageDescriptor(image); } private static String getViewName(int type, String name) { String title = "TCF"; switch (type) { case TYPE_PROCESS_CONSOLE: title += " Debug Process Console - " + name; break; case TYPE_PROCESS_TERMINAL: title += " Debug Process Terminal - " + name; break; case TYPE_UART_TERMINAL: title += " Debug Virtual Terminal - " + name; break; case TYPE_CMD_LINE: title += " Debugger Command Line"; break; case TYPE_DPRINTF: title = "Debug Dynamic Printf"; break; } return title; } void onModelConnected() { if (ctx_id != null) { // Change view title to include context name instead of context ID Protocol.invokeLater(new Runnable() { @Override public void run() { if (!model.createNode(ctx_id, this)) return; TCFNode node = (TCFNode)model.getNode(ctx_id); if (node instanceof TCFNodeExecContext) { TCFNodeExecContext exe = (TCFNodeExecContext)node; TCFDataCache<IRunControl.RunControlContext> ctx_cache = exe.getRunContext(); if (!ctx_cache.validate(this)) return; IRunControl.RunControlContext ctx_data = ctx_cache.getData(); if (ctx_data != null && ctx_data.getName() != null) { final String name = ctx_data.getName(); model.asyncExec(new Runnable() { @Override public void run() { setName(getViewName(type, name)); } }); } } } }); } } void write(final int stream_id, byte[] data) { assert Protocol.isDispatchThread(); if (data == null || data.length == 0) return; Message m = new Message(); m.stream_id = stream_id; m.data = data; history.add(m); if (history.size() > 1000) history.removeFirst(); for (ViewPage p : pages) { synchronized (p.inp_queue) { p.inp_queue.add(m); p.inp_queue.notify(); } } } void close() { assert Protocol.isDispatchThread(); Message m = new Message(); for (ViewPage p : pages) { synchronized (p.inp_queue) { p.inp_queue.add(m); p.inp_queue.notify(); } } model.asyncExec(new Runnable() { public void run() { IConsoleManager manager = ConsolePlugin.getDefault().getConsoleManager(); manager.removeConsoles(new IConsole[]{ TCFConsole.this }); } }); } @Override public IPageBookViewPage createPage(IConsoleView view) { return new ViewPage(); } }