blob: 0dfcedf2700d4a66ef5da484085deaefb66f7f34 [file] [log] [blame]
/*
* 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 java.util.concurrent.atomic.AtomicBoolean;
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.ClosedException;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingInputStream;
import jdk.internal.org.jline.utils.NonBlockingReader;
public class PosixPtyTerminal extends AbstractPosixTerminal {
private final InputStream in;
private final OutputStream out;
private final InputStream masterInput;
private final OutputStream masterOutput;
private final NonBlockingInputStream input;
private final OutputStream output;
private final NonBlockingReader reader;
private final PrintWriter writer;
private final Object lock = new Object();
private Thread inputPumpThread;
private Thread outputPumpThread;
private boolean paused = true;
public PosixPtyTerminal(String name, String type, Pty pty, InputStream in, OutputStream out, Charset encoding) throws IOException {
this(name, type, pty, in, out, encoding, SignalHandler.SIG_DFL);
}
public PosixPtyTerminal(String name, String type, Pty pty, InputStream in, OutputStream out, Charset encoding, SignalHandler signalHandler) throws IOException {
this(name, type, pty, in, out, encoding, signalHandler, false);
}
public PosixPtyTerminal(String name, String type, Pty pty, InputStream in, OutputStream out, Charset encoding, SignalHandler signalHandler, boolean paused) throws IOException {
super(name, type, pty, encoding, signalHandler);
this.in = Objects.requireNonNull(in);
this.out = Objects.requireNonNull(out);
this.masterInput = pty.getMasterInput();
this.masterOutput = pty.getMasterOutput();
this.input = new InputStreamWrapper(NonBlocking.nonBlocking(name, pty.getSlaveInput()));
this.output = pty.getSlaveOutput();
this.reader = NonBlocking.nonBlocking(name, input, encoding());
this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
parseInfoCmp();
if (!paused) {
resume();
}
}
public InputStream input() {
return input;
}
public NonBlockingReader reader() {
return reader;
}
public OutputStream output() {
return output;
}
public PrintWriter writer() {
return writer;
}
@Override
protected void doClose() throws IOException {
super.doClose();
reader.close();
}
@Override
public boolean canPauseResume() {
return true;
}
@Override
public void pause() {
synchronized (lock) {
paused = true;
}
}
@Override
public void pause(boolean wait) throws InterruptedException {
Thread p1, p2;
synchronized (lock) {
paused = true;
p1 = inputPumpThread;
p2 = outputPumpThread;
}
if (p1 != null) {
p1.interrupt();
}
if (p2 != null) {
p2.interrupt();
}
if (p1 != null) {
p1.join();
}
if (p2 !=null) {
p2.join();
}
}
@Override
public void resume() {
synchronized (lock) {
paused = false;
if (inputPumpThread == null) {
inputPumpThread = new Thread(this::pumpIn, toString() + " input pump thread");
inputPumpThread.setDaemon(true);
inputPumpThread.start();
}
if (outputPumpThread == null) {
outputPumpThread = new Thread(this::pumpOut, toString() + " output pump thread");
outputPumpThread.setDaemon(true);
outputPumpThread.start();
}
}
}
@Override
public boolean paused() {
synchronized (lock) {
return paused;
}
}
private class InputStreamWrapper extends NonBlockingInputStream {
private final NonBlockingInputStream in;
private final AtomicBoolean closed = new AtomicBoolean();
protected InputStreamWrapper(NonBlockingInputStream in) {
this.in = in;
}
@Override
public int read(long timeout, boolean isPeek) throws IOException {
if (closed.get()) {
throw new ClosedException();
}
return in.read(timeout, isPeek);
}
@Override
public void close() throws IOException {
closed.set(true);
}
}
private void pumpIn() {
try {
for (;;) {
synchronized (lock) {
if (paused) {
inputPumpThread = null;
return;
}
}
int b = in.read();
if (b < 0) {
input.close();
break;
}
masterOutput.write(b);
masterOutput.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
synchronized (lock) {
inputPumpThread = null;
}
}
}
private void pumpOut() {
try {
for (;;) {
synchronized (lock) {
if (paused) {
outputPumpThread = null;
return;
}
}
int b = masterInput.read();
if (b < 0) {
input.close();
break;
}
out.write(b);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
synchronized (lock) {
outputPumpThread = null;
}
}
try {
close();
} catch (Throwable t) {
// Ignore
}
}
}