/*
* This file is part of lanterna (http://code.google.com/p/lanterna/).
*
* lanterna is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2010-2020 Martin Berglund
*/
package com.googlecode.lanterna.terminal.ansi;
import com.googlecode.lanterna.input.KeyStroke;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
Base class for all terminals that generally behave like Unix terminals. This class defined a number of abstract
methods that needs to be implemented which are all used to setup the terminal environment (turning off echo,
canonical mode, etc) and also a control variable for how to react to CTRL+c keystroke.
/**
* Base class for all terminals that generally behave like Unix terminals. This class defined a number of abstract
* methods that needs to be implemented which are all used to setup the terminal environment (turning off echo,
* canonical mode, etc) and also a control variable for how to react to CTRL+c keystroke.
*/
public abstract class UnixLikeTerminal extends ANSITerminal {
This enum lets you control how Lanterna will handle a ctrl+c keystroke from the user.
/**
* This enum lets you control how Lanterna will handle a ctrl+c keystroke from the user.
*/
public enum CtrlCBehaviour {
Pressing ctrl+c doesn't kill the application, it will be added to the input queue as any other key stroke
/**
* Pressing ctrl+c doesn't kill the application, it will be added to the input queue as any other key stroke
*/
TRAP,
Pressing ctrl+c will restore the terminal and kill the application as it normally does with terminal applications. Lanterna will restore the terminal and then call System.exit(1)
for this. /**
* Pressing ctrl+c will restore the terminal and kill the application as it normally does with terminal
* applications. Lanterna will restore the terminal and then call {@code System.exit(1)} for this.
*/
CTRL_C_KILLS_APPLICATION,
}
private final CtrlCBehaviour terminalCtrlCBehaviour;
private final boolean catchSpecialCharacters;
private final Thread shutdownHook;
private boolean acquired;
protected UnixLikeTerminal(InputStream terminalInput,
OutputStream terminalOutput,
Charset terminalCharset,
CtrlCBehaviour terminalCtrlCBehaviour) throws IOException {
super(terminalInput, terminalOutput, terminalCharset);
this.acquired = false;
String catchSpecialCharactersPropValue = System.getProperty(
"com.googlecode.lanterna.terminal.UnixTerminal.catchSpecialCharacters",
"");
this.catchSpecialCharacters = !"false".equals(catchSpecialCharactersPropValue.trim().toLowerCase());
this.terminalCtrlCBehaviour = terminalCtrlCBehaviour;
shutdownHook = new Thread("Lanterna STTY restore") {
@Override
public void run() {
exitPrivateModeAndRestoreState();
}
};
acquire();
}
Effectively taking over the terminal and enabling it for Lanterna to use, by turning off echo and canonical mode, adding resize listeners and optionally trap unix signals. This should be called automatically by the constructor of any end-user class extending from UnixLikeTerminal
Throws: - IOException – If there was an I/O error
/**
* Effectively taking over the terminal and enabling it for Lanterna to use, by turning off echo and canonical mode,
* adding resize listeners and optionally trap unix signals. This should be called automatically by the constructor
* of any end-user class extending from {@link UnixLikeTerminal}
* @throws IOException If there was an I/O error
*/
protected void acquire() throws IOException {
//Make sure to set an initial size
onResized(80, 24);
saveTerminalSettings();
canonicalMode(false);
keyEchoEnabled(false);
if(catchSpecialCharacters) {
keyStrokeSignalsEnabled(false);
}
registerTerminalResizeListener(new Runnable() {
@Override
public void run() {
// This will trigger a resize notification as the size will be different than before
try {
getTerminalSize();
}
catch(IOException ignore) {
// Not much to do here, we can't re-throw it
}
}
});
Runtime.getRuntime().addShutdownHook(shutdownHook);
acquired = true;
}
@Override
public void close() throws IOException {
exitPrivateModeAndRestoreState();
Runtime.getRuntime().removeShutdownHook(shutdownHook);
acquired = false;
super.close();
}
@Override
public KeyStroke pollInput() throws IOException {
//Check if we have ctrl+c coming
KeyStroke key = super.pollInput();
isCtrlC(key);
return key;
}
@Override
public KeyStroke readInput() throws IOException {
//Check if we have ctrl+c coming
KeyStroke key = super.readInput();
isCtrlC(key);
return key;
}
protected CtrlCBehaviour getTerminalCtrlCBehaviour() {
return terminalCtrlCBehaviour;
}
protected abstract void registerTerminalResizeListener(Runnable onResize) throws IOException;
Stores the current terminal device settings (the ones that are modified through this interface) so that they can be restored later using restoreTerminalSettings()
Throws: - IOException – If there was an I/O error when altering the terminal environment
/**
* Stores the current terminal device settings (the ones that are modified through this interface) so that they can
* be restored later using {@link #restoreTerminalSettings()}
* @throws IOException If there was an I/O error when altering the terminal environment
*/
protected abstract void saveTerminalSettings() throws IOException;
Restores the terminal settings from last time saveTerminalSettings()
was called Throws: - IOException – If there was an I/O error when altering the terminal environment
/**
* Restores the terminal settings from last time {@link #saveTerminalSettings()} was called
* @throws IOException If there was an I/O error when altering the terminal environment
*/
protected abstract void restoreTerminalSettings() throws IOException;
private void restoreTerminalSettingsAndKeyStrokeSignals() throws IOException {
restoreTerminalSettings();
if(catchSpecialCharacters) {
keyStrokeSignalsEnabled(true);
}
}
Enables or disable key echo mode, which means when the user press a key, the terminal will immediately print that
key to the terminal. Normally for Lanterna, this should be turned off so the software can take the key as an
input event, put it on the input queue and then depending on the code decide what to do with it.
Params: - enabled –
true
if key echo should be enabled, false
otherwise
Throws: - IOException – If there was an I/O error when altering the terminal environment
/**
* Enables or disable key echo mode, which means when the user press a key, the terminal will immediately print that
* key to the terminal. Normally for Lanterna, this should be turned off so the software can take the key as an
* input event, put it on the input queue and then depending on the code decide what to do with it.
* @param enabled {@code true} if key echo should be enabled, {@code false} otherwise
* @throws IOException If there was an I/O error when altering the terminal environment
*/
protected abstract void keyEchoEnabled(boolean enabled) throws IOException;
In canonical mode, data are accumulated in a line editing buffer, and do not become "available for reading" until
line editing has been terminated by the user sending a line delimiter character. This is usually the default mode
for a terminal. Lanterna wants to read each character as they are typed, without waiting for the final newline,
so it will attempt to turn canonical mode off on initialization.
Params: - enabled –
true
if canonical input mode should be enabled, false
otherwise
Throws: - IOException – If there was an I/O error when altering the terminal environment
/**
* In canonical mode, data are accumulated in a line editing buffer, and do not become "available for reading" until
* line editing has been terminated by the user sending a line delimiter character. This is usually the default mode
* for a terminal. Lanterna wants to read each character as they are typed, without waiting for the final newline,
* so it will attempt to turn canonical mode off on initialization.
* @param enabled {@code true} if canonical input mode should be enabled, {@code false} otherwise
* @throws IOException If there was an I/O error when altering the terminal environment
*/
protected abstract void canonicalMode(boolean enabled) throws IOException;
This method causes certain keystrokes (at the moment only ctrl+c) to be passed in to the program as a regular KeyStroke
instead of as a signal to the JVM process. For example, ctrl+c will normally send an interrupt that causes the JVM to shut down, but this method will make it pass
in ctrl+c as a regular KeyStroke
instead. You can of course still make ctrl+c kill the application through your own input handling if you like.
Please note that this method is called automatically by lanterna to disable signals unless you define a system
property "com.googlecode.lanterna.terminal.UnixTerminal.catchSpecialCharacters" and set it to the string "false".
Params: - enabled – Pass in
true
if you want keystrokes to generate system signals (like process interrupt), false
if you want lanterna to catch and interpret these keystrokes are regular keystrokes
Throws: - IOException – If there was an I/O error when attempting to disable special characters
See Also:
/**
* This method causes certain keystrokes (at the moment only ctrl+c) to be passed in to the program as a regular
* {@link com.googlecode.lanterna.input.KeyStroke} instead of as a signal to the JVM process. For example,
* <i>ctrl+c</i> will normally send an interrupt that causes the JVM to shut down, but this method will make it pass
* in <i>ctrl+c</i> as a regular {@link com.googlecode.lanterna.input.KeyStroke} instead. You can of course still
* make <i>ctrl+c</i> kill the application through your own input handling if you like.
* <p>
* Please note that this method is called automatically by lanterna to disable signals unless you define a system
* property "com.googlecode.lanterna.terminal.UnixTerminal.catchSpecialCharacters" and set it to the string "false".
* @param enabled Pass in {@code true} if you want keystrokes to generate system signals (like process interrupt),
* {@code false} if you want lanterna to catch and interpret these keystrokes are regular keystrokes
* @throws IOException If there was an I/O error when attempting to disable special characters
* @see UnixLikeTTYTerminal.CtrlCBehaviour
*/
protected abstract void keyStrokeSignalsEnabled(boolean enabled) throws IOException;
private void isCtrlC(KeyStroke key) throws IOException {
if(key != null
&& terminalCtrlCBehaviour == CtrlCBehaviour.CTRL_C_KILLS_APPLICATION
&& key.getCharacter() != null
&& key.getCharacter() == 'c'
&& !key.isAltDown()
&& key.isCtrlDown()) {
if (isInPrivateMode()) {
exitPrivateMode();
}
System.exit(1);
}
}
private void exitPrivateModeAndRestoreState() {
if(!acquired) {
return;
}
try {
if (isInPrivateMode()) {
exitPrivateMode();
}
}
catch(IOException ignored) {}
catch(IllegalStateException ignored) {} // still possible!
try {
restoreTerminalSettingsAndKeyStrokeSignals();
}
catch(IOException ignored) {}
}
}