-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRawConsoleInput.java
More file actions
290 lines (254 loc) · 12.1 KB
/
RawConsoleInput.java
File metadata and controls
290 lines (254 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import com.sun.jna.*;
import com.sun.jna.ptr.IntByReference;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.util.Arrays;
import java.util.List;
/**
* A JNA based driver for reading single characters from the console.
*
* <p>This class is used for console mode programs.
* It supports non-blocking reads of single key strokes without echo.
*/
public class RawConsoleInput {
private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
private static final int invalidKey = 0xFFFE;
private static final String invalidKeyStr = String.valueOf((char)invalidKey);
private static boolean initDone;
private static boolean stdinIsConsole;
private static boolean consoleModeAltered;
/**
* Reads a character from the console without echo.
*
* @param wait
* <code>true</code> to wait until an input character is available,
* <code>false</code> to return immediately if no character is available.
* @return
* -2 if <code>wait</code> is <code>false</code> and no character is available.
* -1 on EOF.
* Otherwise an Unicode character code within the range 0 to 0xFFFF.
*/
public static int read (boolean wait) throws IOException {
if (isWindows) {
return readWindows(wait); }
else {
return readUnix(wait); }}
/**
* Resets console mode to normal line mode with echo.
*
* <p>On Windows this method re-enables Ctrl-C processing.
*
* <p>On Unix this method switches the console back to echo mode.
* read() leaves the console in non-echo mode.
*/
public static void resetConsoleMode() throws IOException {
if (isWindows) {
resetConsoleModeWindows(); }
else {
resetConsoleModeUnix(); }}
private static void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook( new Thread() {
public void run() {
shutdownHook(); }}); }
private static void shutdownHook() {
try {
resetConsoleMode(); }
catch (Exception e) {}}
//--- Windows ------------------------------------------------------------------
// The Windows version uses _kbhit() and _getwch() from msvcrt.dll.
private static Msvcrt msvcrt;
private static Kernel32 kernel32;
private static Pointer consoleHandle;
private static int originalConsoleMode;
private static int readWindows (boolean wait) throws IOException {
initWindows();
if (!stdinIsConsole) {
int c = msvcrt.getwchar();
if (c == 0xFFFF) {
c = -1; }
return c; }
consoleModeAltered = true;
setConsoleMode(consoleHandle, originalConsoleMode & ~Kernel32Defs.ENABLE_PROCESSED_INPUT);
// ENABLE_PROCESSED_INPUT must remain off to prevent Ctrl-C from beeing processed by the system
// while the program is not within getwch().
if (!wait && msvcrt._kbhit() == 0) {
return -2; } // no key available
return getwch(); }
private static int getwch() {
int c = msvcrt._getwch();
if (c == 0 || c == 0xE0) { // Function key or arrow key
c = msvcrt._getwch();
if (c >= 0 && c <= 0x18FF) {
return 0xE000 + c; } // construct key code in private Unicode range
return invalidKey; }
if (c < 0 || c > 0xFFFF) {
return invalidKey; }
return c; } // normal key
private static synchronized void initWindows() throws IOException {
if (initDone) {
return; }
msvcrt = (Msvcrt)Native.loadLibrary("msvcrt", Msvcrt.class);
kernel32 = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
try {
consoleHandle = getStdInputHandle();
originalConsoleMode = getConsoleMode(consoleHandle);
stdinIsConsole = true; }
catch (IOException e) {
stdinIsConsole = false; }
if (stdinIsConsole) {
registerShutdownHook(); }
initDone = true; }
private static Pointer getStdInputHandle() throws IOException {
Pointer handle = kernel32.GetStdHandle(Kernel32Defs.STD_INPUT_HANDLE);
if (Pointer.nativeValue(handle) == 0 || Pointer.nativeValue(handle) == Kernel32Defs.INVALID_HANDLE_VALUE) {
throw new IOException("GetStdHandle(STD_INPUT_HANDLE) failed."); }
return handle; }
private static int getConsoleMode (Pointer handle) throws IOException {
IntByReference mode = new IntByReference();
int rc = kernel32.GetConsoleMode(handle, mode);
if (rc == 0) {
throw new IOException("GetConsoleMode() failed."); }
return mode.getValue(); }
private static void setConsoleMode (Pointer handle, int mode) throws IOException {
int rc = kernel32.SetConsoleMode(handle, mode);
if (rc == 0) {
throw new IOException("SetConsoleMode() failed."); }}
private static void resetConsoleModeWindows() throws IOException {
if (!initDone || !stdinIsConsole || !consoleModeAltered) {
return; }
setConsoleMode(consoleHandle, originalConsoleMode);
consoleModeAltered = false; }
private static interface Msvcrt extends Library {
int _kbhit();
int _getwch();
int getwchar(); }
private static class Kernel32Defs {
static final int STD_INPUT_HANDLE = -10;
static final long INVALID_HANDLE_VALUE = (Pointer.SIZE == 8) ? -1 : 0xFFFFFFFFL;
static final int ENABLE_PROCESSED_INPUT = 0x0001;
static final int ENABLE_LINE_INPUT = 0x0002;
static final int ENABLE_ECHO_INPUT = 0x0004;
static final int ENABLE_WINDOW_INPUT = 0x0008; }
private static interface Kernel32 extends Library {
int GetConsoleMode (Pointer hConsoleHandle, IntByReference lpMode);
int SetConsoleMode (Pointer hConsoleHandle, int dwMode);
Pointer GetStdHandle (int nStdHandle); }
//--- Unix ---------------------------------------------------------------------
// The Unix version uses tcsetattr() to switch the console to non-canonical mode,
// System.in.available() to check whether data is available and System.in.read()
// to read bytes from the console.
// A CharsetDecoder is used to convert bytes to characters.
private static final int stdinFd = 0;
private static Libc libc;
private static CharsetDecoder charsetDecoder;
private static Termios originalTermios;
private static Termios rawTermios;
private static Termios intermediateTermios;
private static int readUnix (boolean wait) throws IOException {
initUnix();
if (!stdinIsConsole) { // STDIN is not a console
return readSingleCharFromByteStream(System.in); }
consoleModeAltered = true;
setTerminalAttrs(stdinFd, rawTermios); // switch off canonical mode, echo and signals
try {
if (!wait && System.in.available() == 0) {
return -2; } // no input available
return readSingleCharFromByteStream(System.in); }
finally {
setTerminalAttrs(stdinFd, intermediateTermios); }} // reset some console attributes
private static Termios getTerminalAttrs (int fd) throws IOException {
Termios termios = new Termios();
try {
int rc = libc.tcgetattr(fd, termios);
if (rc != 0) {
throw new RuntimeException("tcgetattr() failed."); }}
catch (LastErrorException e) {
throw new IOException("tcgetattr() failed.", e); }
return termios; }
private static void setTerminalAttrs (int fd, Termios termios) throws IOException {
try {
int rc = libc.tcsetattr(fd, LibcDefs.TCSANOW, termios);
if (rc != 0) {
throw new RuntimeException("tcsetattr() failed."); }}
catch (LastErrorException e) {
throw new IOException("tcsetattr() failed.", e); }}
private static int readSingleCharFromByteStream (InputStream inputStream) throws IOException {
byte[] inBuf = new byte[4];
int inLen = 0;
while (true) {
if (inLen >= inBuf.length) { // input buffer overflow
return invalidKey; }
int b = inputStream.read(); // read next byte
if (b == -1) { // EOF
return -1; }
inBuf[inLen++] = (byte)b;
int c = decodeCharFromBytes(inBuf, inLen);
if (c != -1) {
return c; }}}
// (This method is synchronized because the charsetDecoder must only be used by a single thread at once.)
private static synchronized int decodeCharFromBytes (byte[] inBytes, int inLen) {
charsetDecoder.reset();
charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE);
charsetDecoder.replaceWith(invalidKeyStr);
ByteBuffer in = ByteBuffer.wrap(inBytes, 0, inLen);
CharBuffer out = CharBuffer.allocate(1);
charsetDecoder.decode(in, out, false);
if (out.position() == 0) {
return -1; }
return out.get(0); }
private static synchronized void initUnix() throws IOException {
if (initDone) {
return; }
libc = (Libc)Native.loadLibrary("c", Libc.class);
stdinIsConsole = libc.isatty(stdinFd) == 1;
charsetDecoder = Charset.defaultCharset().newDecoder();
if (stdinIsConsole) {
originalTermios = getTerminalAttrs(stdinFd);
rawTermios = new Termios(originalTermios);
rawTermios.c_lflag &= ~(LibcDefs.ICANON | LibcDefs.ECHO | LibcDefs.ECHONL | LibcDefs.ISIG);
intermediateTermios = new Termios(rawTermios);
intermediateTermios.c_lflag |= LibcDefs.ICANON;
// Canonical mode can be switched off between the read() calls, but echo must remain disabled.
registerShutdownHook(); }
initDone = true; }
private static void resetConsoleModeUnix() throws IOException {
if (!initDone || !stdinIsConsole || !consoleModeAltered) {
return; }
setTerminalAttrs(stdinFd, originalTermios);
consoleModeAltered = false; }
protected static class Termios extends Structure { // termios.h
public int c_iflag;
public int c_oflag;
public int c_cflag;
public int c_lflag;
public byte c_line;
public byte[] filler = new byte[64]; // actual length is platform dependent
@Override protected List<?> getFieldOrder() {
return Arrays.asList("c_iflag", "c_oflag", "c_cflag", "c_lflag", "c_line", "filler"); }
Termios() {}
Termios (Termios t) {
c_iflag = t.c_iflag;
c_oflag = t.c_oflag;
c_cflag = t.c_cflag;
c_lflag = t.c_lflag;
c_line = t.c_line;
filler = t.filler.clone(); }}
private static class LibcDefs {
// termios.h
static final int ISIG = 0000001;
static final int ICANON = 0000002;
static final int ECHO = 0000010;
static final int ECHONL = 0000100;
static final int TCSANOW = 0; }
private static interface Libc extends Library {
// termios.h
int tcgetattr (int fd, Termios termios) throws LastErrorException;
int tcsetattr (int fd, int opt, Termios termios) throws LastErrorException;
// unistd.h
int isatty (int fd); }
}