/*
 * Copyright (c) 2002-2018, the original author or authors.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * https://opensource.org/licenses/BSD-3-Clause
 */
package jdk.internal.org.jline.terminal.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Objects;

import jdk.internal.org.jline.terminal.Attributes;
import jdk.internal.org.jline.terminal.Attributes.ControlChar;
import jdk.internal.org.jline.terminal.Attributes.InputFlag;
import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
import jdk.internal.org.jline.terminal.Attributes.OutputFlag;
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingPumpInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;

Abstract terminal with support for line discipline. The Terminal interface represents the slave side of a PTY, but implementations derived from this class will handle both the slave and master side of things. In order to correctly handle line discipline, the terminal needs to read the input in advance in order to raise the signals as fast as possible. For example, when the user hits Ctrl+C, we can't wait until the application consumes all the read events. The same applies to echoing, when enabled, as the echoing has to happen as soon as the user hit the keyboard, and not only when the application running in the terminal processes the input.
/** * Abstract terminal with support for line discipline. * The {@link Terminal} interface represents the slave * side of a PTY, but implementations derived from this class * will handle both the slave and master side of things. * * In order to correctly handle line discipline, the terminal * needs to read the input in advance in order to raise the * signals as fast as possible. * For example, when the user hits Ctrl+C, we can't wait until * the application consumes all the read events. * The same applies to echoing, when enabled, as the echoing * has to happen as soon as the user hit the keyboard, and not * only when the application running in the terminal processes * the input. */
public class LineDisciplineTerminal extends AbstractTerminal { private static final String DEFAULT_TERMINAL_ATTRIBUTES = "speed 9600 baud; 24 rows; 80 columns;\n" + "lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl\n" + "\t-echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo\n" + "\t-extproc\n" + "iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel iutf8\n" + "\t-ignbrk brkint -inpck -ignpar -parmrk\n" + "oflags: opost onlcr -oxtabs -onocr -onlret\n" + "cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow\n" + "\t-dtrflow -mdmbuf\n" + "cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;\n" + "\teol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;\n" + "\tmin = 1; quit = ^\\; reprint = ^R; start = ^Q; status = ^T;\n" + "\tstop = ^S; susp = ^Z; time = 0; werase = ^W;\n"; private static final int PIPE_SIZE = 1024; /* * Master output stream */ protected final OutputStream masterOutput; /* * Slave input pipe write side */ protected final OutputStream slaveInputPipe; /* * Slave streams */ protected final NonBlockingPumpInputStream slaveInput; protected final NonBlockingReader slaveReader; protected final PrintWriter slaveWriter; protected final OutputStream slaveOutput;
Console data
/** * Console data */
protected final Attributes attributes; protected final Size size; public LineDisciplineTerminal(String name, String type, OutputStream masterOutput, Charset encoding) throws IOException { this(name, type, masterOutput, encoding, SignalHandler.SIG_DFL); } public LineDisciplineTerminal(String name, String type, OutputStream masterOutput, Charset encoding, SignalHandler signalHandler) throws IOException { super(name, type, encoding, signalHandler); NonBlockingPumpInputStream input = NonBlocking.nonBlockingPumpInputStream(PIPE_SIZE); this.slaveInputPipe = input.getOutputStream(); this.slaveInput = input; this.slaveReader = NonBlocking.nonBlocking(getName(), slaveInput, encoding()); this.slaveOutput = new FilteringOutputStream(); this.slaveWriter = new PrintWriter(new OutputStreamWriter(slaveOutput, encoding())); this.masterOutput = masterOutput; this.attributes = ExecPty.doGetAttr(DEFAULT_TERMINAL_ATTRIBUTES); this.size = new Size(160, 50); parseInfoCmp(); } public NonBlockingReader reader() { return slaveReader; } public PrintWriter writer() { return slaveWriter; } @Override public InputStream input() { return slaveInput; } @Override public OutputStream output() { return slaveOutput; } public Attributes getAttributes() { Attributes attr = new Attributes(); attr.copy(attributes); return attr; } public void setAttributes(Attributes attr) { attributes.copy(attr); } public Size getSize() { Size sz = new Size(); sz.copy(size); return sz; } public void setSize(Size sz) { size.copy(sz); } @Override public void raise(Signal signal) { Objects.requireNonNull(signal); // Do not call clear() atm as this can cause // deadlock between reading / writing threads // TODO: any way to fix that ? /* if (!attributes.getLocalFlag(LocalFlag.NOFLSH)) { try { slaveReader.clear(); } catch (IOException e) { // Ignore } } */ echoSignal(signal); super.raise(signal); }
Master input processing. All data coming to the terminal should be provided using this method.
Params:
  • c – the input byte
Throws:
/** * Master input processing. * All data coming to the terminal should be provided * using this method. * * @param c the input byte * @throws IOException if anything wrong happens */
public void processInputByte(int c) throws IOException { boolean flushOut = doProcessInputByte(c); slaveInputPipe.flush(); if (flushOut) { masterOutput.flush(); } } public void processInputBytes(byte[] input) throws IOException { processInputBytes(input, 0, input.length); } public void processInputBytes(byte[] input, int offset, int length) throws IOException { boolean flushOut = false; for (int i = 0; i < length; i++) { flushOut |= doProcessInputByte(input[offset + i]); } slaveInputPipe.flush(); if (flushOut) { masterOutput.flush(); } } protected boolean doProcessInputByte(int c) throws IOException { if (attributes.getLocalFlag(LocalFlag.ISIG)) { if (c == attributes.getControlChar(ControlChar.VINTR)) { raise(Signal.INT); return false; } else if (c == attributes.getControlChar(ControlChar.VQUIT)) { raise(Signal.QUIT); return false; } else if (c == attributes.getControlChar(ControlChar.VSUSP)) { raise(Signal.TSTP); return false; } else if (c == attributes.getControlChar(ControlChar.VSTATUS)) { raise(Signal.INFO); } } if (c == '\r') { if (attributes.getInputFlag(InputFlag.IGNCR)) { return false; } if (attributes.getInputFlag(InputFlag.ICRNL)) { c = '\n'; } } else if (c == '\n' && attributes.getInputFlag(InputFlag.INLCR)) { c = '\r'; } boolean flushOut = false; if (attributes.getLocalFlag(LocalFlag.ECHO)) { processOutputByte(c); flushOut = true; } slaveInputPipe.write(c); return flushOut; }
Master output processing. All data going to the master should be provided by this method.
Params:
  • c – the output byte
Throws:
/** * Master output processing. * All data going to the master should be provided by this method. * * @param c the output byte * @throws IOException if anything wrong happens */
protected void processOutputByte(int c) throws IOException { if (attributes.getOutputFlag(OutputFlag.OPOST)) { if (c == '\n') { if (attributes.getOutputFlag(OutputFlag.ONLCR)) { masterOutput.write('\r'); masterOutput.write('\n'); return; } } } masterOutput.write(c); } protected void processIOException(IOException ioException) { this.slaveInput.setIoException(ioException); } public void close() throws IOException { super.close(); try { slaveReader.close(); } finally { try { slaveInputPipe.close(); } finally { try { } finally { slaveWriter.close(); } } } } private class FilteringOutputStream extends OutputStream { @Override public void write(int b) throws IOException { processOutputByte(b); flush(); } @Override public void write(byte[] b, int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } for (int i = 0 ; i < len ; i++) { processOutputByte(b[off + i]); } flush(); } @Override public void flush() throws IOException { masterOutput.flush(); } @Override public void close() throws IOException { masterOutput.close(); } } }