Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / terminal / ansi / UnixLikeTerminal.java
1 package com.googlecode.lanterna.terminal.ansi;
2
3 import java.io.BufferedReader;
4 import java.io.ByteArrayInputStream;
5 import java.io.ByteArrayOutputStream;
6 import java.io.File;
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;
15
16 import com.googlecode.lanterna.input.KeyStroke;
17
18 /**
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.
23 * <p>
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.
27 *
28 * @author Andreas
29 * @author Martin
30 */
31 public abstract class UnixLikeTerminal extends ANSITerminal {
32
33 /**
34 * This enum lets you control how Lanterna will handle a ctrl+c keystroke from the user.
35 */
36 public enum CtrlCBehaviour {
37 /**
38 * Pressing ctrl+c doesn't kill the application, it will be added to the input queue as any other key stroke
39 */
40 TRAP,
41 /**
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.
44 */
45 CTRL_C_KILLS_APPLICATION,
46 }
47
48 protected final CtrlCBehaviour terminalCtrlCBehaviour;
49 protected final File ttyDev;
50 private String sttyStatusToRestore;
51
52 /**
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.
56 *
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
61 * details
62 * @param ttyDev File to redirect standard input from in exec(), if not null.
63 */
64 @SuppressWarnings({"SameParameterValue", "WeakerAccess"})
65 public UnixLikeTerminal(
66 InputStream terminalInput,
67 OutputStream terminalOutput,
68 Charset terminalCharset,
69 CtrlCBehaviour terminalCtrlCBehaviour,
70 File ttyDev) {
71 super(terminalInput, terminalOutput, terminalCharset);
72 this.terminalCtrlCBehaviour = terminalCtrlCBehaviour;
73 this.sttyStatusToRestore = null;
74 this.ttyDev = ttyDev;
75 }
76
77 protected String exec(String... cmd) throws IOException {
78 if (ttyDev != null) {
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() };
87 }
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();
96 }
97 ByteArrayInputStream stdoutBufferInputStream = new ByteArrayInputStream(stdoutBuffer.toByteArray());
98 BufferedReader reader = new BufferedReader(new InputStreamReader(stdoutBufferInputStream));
99 StringBuilder builder = new StringBuilder();
100 String line;
101 while((line = reader.readLine()) != null) {
102 builder.append(line);
103 }
104 reader.close();
105 return builder.toString();
106 }
107
108 @Override
109 public KeyStroke pollInput() throws IOException {
110 //Check if we have ctrl+c coming
111 KeyStroke key = super.pollInput();
112 isCtrlC(key);
113 return key;
114 }
115
116 @Override
117 public KeyStroke readInput() throws IOException {
118 //Check if we have ctrl+c coming
119 KeyStroke key = super.readInput();
120 isCtrlC(key);
121 return key;
122 }
123
124 private void isCtrlC(KeyStroke key) throws IOException {
125 if(key != null
126 && terminalCtrlCBehaviour == CtrlCBehaviour.CTRL_C_KILLS_APPLICATION
127 && key.getCharacter() != null
128 && key.getCharacter() == 'c'
129 && !key.isAltDown()
130 && key.isCtrlDown()) {
131
132 exitPrivateMode();
133 System.exit(1);
134 }
135 }
136
137 protected void setupWinResizeHandler() {
138 try {
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() {
143 @Override
144 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
145 if("handle".equals(method.getName())) {
146 getTerminalSize();
147 }
148 return null;
149 }
150 });
151 m.invoke(null, signalClass.getConstructor(String.class).newInstance("WINCH"), windowResizeHandler);
152 }
153 }
154 } catch(Throwable e) {
155 System.err.println(e.getMessage());
156 }
157 }
158
159 protected void setupShutdownHook() {
160 Runtime.getRuntime().addShutdownHook(new Thread("Lanterna STTY restore") {
161 @Override
162 public void run() {
163 try {
164 if (isInPrivateMode()) {
165 exitPrivateMode();
166 }
167 }
168 catch(IOException ignored) {}
169 catch(IllegalStateException ignored) {} // still possible!
170
171 try {
172 restoreSTTY();
173 }
174 catch(IOException ignored) {}
175 }
176 });
177 }
178
179 /**
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.
183 *
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
187 */
188 public void setCBreak(boolean cbreakOn) throws IOException {
189 sttyICanon(!cbreakOn);
190 }
191
192 /**
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
195 * mode.
196 *
197 * @param echoOn true if keyboard input will immediately echo, false if it's hidden
198 * @throws IOException
199 */
200 public void setEcho(boolean echoOn) throws IOException {
201 sttyKeyEcho(echoOn);
202 }
203
204 protected void saveSTTY() throws IOException {
205 if(sttyStatusToRestore == null) {
206 sttyStatusToRestore = sttySave();
207 }
208 }
209
210 protected synchronized void restoreSTTY() throws IOException {
211 if(sttyStatusToRestore != null) {
212 sttyRestore( sttyStatusToRestore );
213 sttyStatusToRestore = null;
214 }
215 }
216
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;
223
224 }