diff --git a/builtins/src/main/java/org/jline/builtins/Tmux.java b/builtins/src/main/java/org/jline/builtins/Tmux.java index 464e9487d..88944cccb 100644 --- a/builtins/src/main/java/org/jline/builtins/Tmux.java +++ b/builtins/src/main/java/org/jline/builtins/Tmux.java @@ -1865,8 +1865,8 @@ public void write(int b) throws IOException { masterOutput, null) { @Override - public void close() throws IOException { - super.close(); + protected void doClose() throws IOException { + super.doClose(); closer.accept(VirtualConsole.this); } }; diff --git a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java index adab85ace..978077a36 100644 --- a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java +++ b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java @@ -19,8 +19,10 @@ import java.nio.charset.UnsupportedCharsetException; import java.util.Optional; import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicReference; import org.jline.terminal.impl.AbstractPosixTerminal; +import org.jline.terminal.impl.AbstractTerminal; import org.jline.terminal.impl.DumbTerminal; import org.jline.terminal.impl.ExecPty; import org.jline.terminal.impl.ExternalTerminal; @@ -84,6 +86,8 @@ public static TerminalBuilder builder() { return new TerminalBuilder(); } + private static final AtomicReference SYSTEM_TERMINAL = new AtomicReference<>(); + private String name; private InputStream in; private OutputStream out; @@ -311,6 +315,7 @@ private Terminal doBuild() throws IOException { Log.warn("Attributes and size fields are ignored when creating a system terminal"); } IllegalStateException exception = new IllegalStateException("Unable to create a system terminal"); + Terminal terminal = null; if (OSUtils.IS_WINDOWS) { boolean cygwinTerm = "cygwin".equals(System.getenv("TERM")); boolean ansiPassThrough = OSUtils.IS_CONEMU; @@ -325,53 +330,53 @@ private Terminal doBuild() throws IOException { if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) { type = "xterm-256color"; } - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + terminal = new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); } catch (IOException e) { // Ignore if not a tty Log.debug("Error creating EXEC based terminal: ", e.getMessage(), e); exception.addSuppressed(e); } } - if (jna) { + if (terminal == null && jna) { try { - return load(JnaSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused); + terminal = load(JnaSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused); } catch (Throwable t) { Log.debug("Error creating JNA based terminal: ", t.getMessage(), t); exception.addSuppressed(t); } } - if (jansi) { + if (terminal == null && jansi) { try { - return load(JansiSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused); + terminal = load(JansiSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused); } catch (Throwable t) { Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t); exception.addSuppressed(t); } } } else { - if (jna) { + if (terminal == null && jna) { try { Pty pty = load(JnaSupport.class).current(); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + terminal = new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); } catch (Throwable t) { // ignore Log.debug("Error creating JNA based terminal: ", t.getMessage(), t); exception.addSuppressed(t); } } - if (jansi) { + if (terminal == null && jansi) { try { Pty pty = load(JansiSupport.class).current(); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + terminal = new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); } catch (Throwable t) { Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t); exception.addSuppressed(t); } } - if (exec) { + if (terminal == null && exec) { try { Pty pty = ExecPty.current(); - return new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); + terminal = new PosixSysTerminal(name, type, pty, encoding, nativeSignals, signalHandler); } catch (Throwable t) { // Ignore if not a tty Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t); @@ -379,7 +384,24 @@ private Terminal doBuild() throws IOException { } } } - if (dumb == null || dumb) { + if (terminal instanceof AbstractTerminal) { + AbstractTerminal t = (AbstractTerminal) terminal; + if (SYSTEM_TERMINAL.compareAndSet(null, t)) { + t.setOnClose(new Runnable() { + @Override + public void run() { + SYSTEM_TERMINAL.compareAndSet(t, null); + } + }); + } else { + exception.addSuppressed(new IllegalStateException("A system terminal is already running. " + + "Make sure to use the created system Terminal on the LineReaderBuilder if you're using one " + + "or that previously created system Terminals have been correctly closed.")); + terminal.close(); + terminal = null; + } + } + if (terminal == null && (dumb == null || dumb)) { // forced colored dumb terminal boolean color = getBoolean(PROP_DUMB_COLOR, false); // detect emacs using the env variable @@ -398,13 +420,15 @@ private Terminal doBuild() throws IOException { Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)"); } } - return new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB, + terminal = new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB, new FileInputStream(FileDescriptor.in), new FileOutputStream(FileDescriptor.out), encoding, signalHandler); - } else { + } + if (terminal == null) { throw exception; } + return terminal; } else { if (jna) { try { diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java index 2a36ea688..5fd0db169 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractPosixTerminal.java @@ -71,8 +71,8 @@ public void setSize(Size size) { } } - public void close() throws IOException { - super.close(); + protected void doClose() throws IOException { + super.doClose(); pty.setAttr(originalAttributes); pty.close(); } diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java index 28a82215b..95b0aff57 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractTerminal.java @@ -43,6 +43,7 @@ public abstract class AbstractTerminal implements Terminal { protected final Map ints = new HashMap<>(); protected final Map strings = new HashMap<>(); protected Status status; + protected Runnable onClose; public AbstractTerminal(String name, String type) throws IOException { this(name, type, null, SignalHandler.SIG_DFL); @@ -57,6 +58,10 @@ public AbstractTerminal(String name, String type, Charset encoding, SignalHandle } } + public void setOnClose(Runnable onClose) { + this.onClose = onClose; + } + public Status getStatus() { return getStatus(true); } @@ -85,7 +90,17 @@ public void raise(Signal signal) { } } - public void close() throws IOException { + public final void close() throws IOException { + try { + doClose(); + } finally { + if (onClose != null) { + onClose.run(); + } + } + } + + protected void doClose() throws IOException { if (status != null) { status.update(null); flush(); diff --git a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java index 586d61353..edc6a24f9 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/AbstractWindowsTerminal.java @@ -213,8 +213,8 @@ public void setSize(Size size) { throw new UnsupportedOperationException("Can not resize windows terminal"); } - public void close() throws IOException { - super.close(); + protected void doClose() throws IOException { + super.doClose(); closing = true; if (pump != null) { pump.interrupt(); diff --git a/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java index bea0c7c7c..0298bc32d 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/ExternalTerminal.java @@ -83,10 +83,10 @@ public ExternalTerminal(String name, String type, } } - public void close() throws IOException { + protected void doClose() throws IOException { if (closed.compareAndSet(false, true)) { pause(); - super.close(); + super.doClose(); } } diff --git a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java index 000a6e67b..151b0d4b9 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/LineDisciplineTerminal.java @@ -257,8 +257,8 @@ protected void processIOException(IOException ioException) { this.slaveInput.setIoException(ioException); } - public void close() throws IOException { - super.close(); + protected void doClose() throws IOException { + super.doClose(); try { slaveReader.close(); } finally { diff --git a/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java index 9fc9561b1..1b0423a02 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/PosixPtyTerminal.java @@ -80,8 +80,8 @@ public PrintWriter writer() { } @Override - public void close() throws IOException { - super.close(); + protected void doClose() throws IOException { + super.doClose(); reader.close(); } diff --git a/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java index aa6fb3b86..0dddf266c 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java +++ b/terminal/src/main/java/org/jline/terminal/impl/PosixSysTerminal.java @@ -87,12 +87,12 @@ public OutputStream output() { } @Override - public void close() throws IOException { + protected void doClose() throws IOException { ShutdownHooks.remove(closer); for (Map.Entry entry : nativeHandlers.entrySet()) { Signals.unregister(entry.getKey().name(), entry.getValue()); } - super.close(); + super.doClose(); // Do not call reader.close() reader.shutdown(); }