1 package com
.googlecode
.lanterna
.terminal
.ansi
;
3 import java
.io
.BufferedReader
;
4 import java
.io
.ByteArrayInputStream
;
5 import java
.io
.ByteArrayOutputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.io
.InputStreamReader
;
10 import java
.io
.OutputStream
;
11 import java
.lang
.reflect
.InvocationHandler
;
12 import java
.lang
.reflect
.Method
;
13 import java
.lang
.reflect
.Proxy
;
14 import java
.nio
.charset
.Charset
;
16 import com
.googlecode
.lanterna
.input
.KeyStroke
;
19 * UnixLikeTerminal extends from ANSITerminal and defines functionality that is common to
20 * {@code UnixTerminal} and {@code CygwinTerminal}, like setting tty modes; echo, cbreak
21 * and minimum characters for reading as well as a shutdown hook to set the tty back to
22 * original state at the end.
24 * If requested, it handles Control-C input to terminate the program, and hooks
25 * into Unix WINCH signal to detect when the user has resized the terminal,
26 * if supported by the JVM.
31 public abstract class UnixLikeTerminal
extends ANSITerminal
{
34 * This enum lets you control how Lanterna will handle a ctrl+c keystroke from the user.
36 public enum CtrlCBehaviour
{
38 * Pressing ctrl+c doesn't kill the application, it will be added to the input queue as any other key stroke
42 * Pressing ctrl+c will restore the terminal and kill the application as it normally does with terminal
43 * applications. Lanterna will restore the terminal and then call {@code System.exit(1)} for this.
45 CTRL_C_KILLS_APPLICATION
,
48 protected final CtrlCBehaviour terminalCtrlCBehaviour
;
49 protected final File ttyDev
;
50 private String sttyStatusToRestore
;
53 * Creates a UnixTerminal using a specified input stream, output stream and character set, with a custom size
54 * querier instead of using the default one. This way you can override size detection (if you want to force the
55 * terminal to a fixed size, for example). You also choose how you want ctrl+c key strokes to be handled.
57 * @param terminalInput Input stream to read terminal input from
58 * @param terminalOutput Output stream to write terminal output to
59 * @param terminalCharset Character set to use when converting characters to bytes
60 * @param terminalCtrlCBehaviour Special settings on how the terminal will behave, see {@code UnixTerminalMode} for more
62 * @param ttyDev File to redirect standard input from in exec(), if not null.
64 @SuppressWarnings({"SameParameterValue", "WeakerAccess"})
65 public UnixLikeTerminal(
66 InputStream terminalInput
,
67 OutputStream terminalOutput
,
68 Charset terminalCharset
,
69 CtrlCBehaviour terminalCtrlCBehaviour
,
71 super(terminalInput
, terminalOutput
, terminalCharset
);
72 this.terminalCtrlCBehaviour
= terminalCtrlCBehaviour
;
73 this.sttyStatusToRestore
= null;
77 protected String
exec(String
... cmd
) throws IOException
{
79 //Here's what we try to do, but that is Java 7+ only:
80 // processBuilder.redirectInput(ProcessBuilder.Redirect.from(ttyDev));
81 //instead, for Java 6, we join the cmd into a scriptlet with redirection
82 //and replace cmd by a call to sh with the scriptlet:
83 StringBuilder sb
= new StringBuilder();
84 for (String arg
: cmd
) { sb
.append(arg
).append(' '); }
85 sb
.append("< ").append(ttyDev
);
86 cmd
= new String
[] { "sh", "-c", sb
.toString() };
88 ProcessBuilder pb
= new ProcessBuilder(cmd
);
89 Process process
= pb
.start();
90 ByteArrayOutputStream stdoutBuffer
= new ByteArrayOutputStream();
91 InputStream stdout
= process
.getInputStream();
92 int readByte
= stdout
.read();
93 while(readByte
>= 0) {
94 stdoutBuffer
.write(readByte
);
95 readByte
= stdout
.read();
97 ByteArrayInputStream stdoutBufferInputStream
= new ByteArrayInputStream(stdoutBuffer
.toByteArray());
98 BufferedReader reader
= new BufferedReader(new InputStreamReader(stdoutBufferInputStream
));
99 StringBuilder builder
= new StringBuilder();
101 while((line
= reader
.readLine()) != null) {
102 builder
.append(line
);
105 return builder
.toString();
109 public KeyStroke
pollInput() throws IOException
{
110 //Check if we have ctrl+c coming
111 KeyStroke key
= super.pollInput();
117 public KeyStroke
readInput() throws IOException
{
118 //Check if we have ctrl+c coming
119 KeyStroke key
= super.readInput();
124 private void isCtrlC(KeyStroke key
) throws IOException
{
126 && terminalCtrlCBehaviour
== CtrlCBehaviour
.CTRL_C_KILLS_APPLICATION
127 && key
.getCharacter() != null
128 && key
.getCharacter() == 'c'
130 && key
.isCtrlDown()) {
137 protected void setupWinResizeHandler() {
139 Class
<?
> signalClass
= Class
.forName("sun.misc.Signal");
140 for(Method m
: signalClass
.getDeclaredMethods()) {
141 if("handle".equals(m
.getName())) {
142 Object windowResizeHandler
= Proxy
.newProxyInstance(getClass().getClassLoader(), new Class
[]{Class
.forName("sun.misc.SignalHandler")}, new InvocationHandler() {
144 public Object
invoke(Object proxy
, Method method
, Object
[] args
) throws Throwable
{
145 if("handle".equals(method
.getName())) {
151 m
.invoke(null, signalClass
.getConstructor(String
.class).newInstance("WINCH"), windowResizeHandler
);
154 } catch(Throwable e
) {
155 System
.err
.println(e
.getMessage());
159 protected void setupShutdownHook() {
160 Runtime
.getRuntime().addShutdownHook(new Thread("Lanterna STTY restore") {
164 if (isInPrivateMode()) {
168 catch(IOException ignored
) {}
169 catch(IllegalStateException ignored
) {} // still possible!
174 catch(IOException ignored
) {}
180 * Enabling cbreak mode will allow you to read user input immediately as the user enters the characters, as opposed
181 * to reading the data in lines as the user presses enter. If you want your program to respond to user input by the
182 * keyboard, you probably want to enable cbreak mode.
184 * @see <a href="http://en.wikipedia.org/wiki/POSIX_terminal_interface">POSIX terminal interface</a>
185 * @param cbreakOn Should cbreak be turned on or not
186 * @throws IOException
188 public void setCBreak(boolean cbreakOn
) throws IOException
{
189 sttyICanon(!cbreakOn
);
193 * Enables or disables keyboard echo, meaning the immediate output of the characters you type on your keyboard. If
194 * your users are going to interact with this application through the keyboard, you probably want to disable echo
197 * @param echoOn true if keyboard input will immediately echo, false if it's hidden
198 * @throws IOException
200 public void setEcho(boolean echoOn
) throws IOException
{
204 protected void saveSTTY() throws IOException
{
205 if(sttyStatusToRestore
== null) {
206 sttyStatusToRestore
= sttySave();
210 protected synchronized void restoreSTTY() throws IOException
{
211 if(sttyStatusToRestore
!= null) {
212 sttyRestore( sttyStatusToRestore
);
213 sttyStatusToRestore
= null;
217 // A couple of system-dependent helpers:
218 protected abstract void sttyKeyEcho(final boolean enable
) throws IOException
;
219 protected abstract void sttyMinimum1CharacterForRead() throws IOException
;
220 protected abstract void sttyICanon(final boolean enable
) throws IOException
;
221 protected abstract String
sttySave() throws IOException
;
222 protected abstract void sttyRestore(String tok
) throws IOException
;