Newer
Older
// Copyright 2015 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
// www.source-code.biz, www.inventec.ch/chdh
//
// This module is multi-licensed and may be used under the terms of any of the following licenses:
//
// LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
// EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
//
// Please contact the author if you need another license.
// This module is provided "as is", without warranties of any kind.
//
// Home page: http://www.source-code.biz/snippets/java/RawConsoleInput
package myutil;
import com.sun.jna.*;
import com.sun.jna.ptr.IntByReference;
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>
* <p>This class is used for console mode programs.
* It supports non-blocking reads of single key strokes without echo.
*/
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
private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
private static final boolean isMacOS = System.getProperty("os.name").toLowerCase().startsWith("mac");
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>
* <p>On Windows this method re-enables Ctrl-C processing.
* <p>
* <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.
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
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.
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
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) {
System.out.println("Not ready");
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) {
System.out.println("Exception in term tcset");
throw new RuntimeException("tcsetattr() failed.");
}
} catch (LastErrorException e) {
System.out.println("Exception in term tcset");
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) {
//System.out.println("IsConsole=" + stdinIsConsole);
originalTermios = getTerminalAttrs(stdinFd);
rawTermios = new Termios(originalTermios);
if (!isMacOS) {
rawTermios.c_lflag &= ~(LibcDefs.ICANON | LibcDefs.ECHO | LibcDefs.ECHONL | LibcDefs.ISIG);
} else {
rawTermios.c_lflag &= ~(LibcDefs.ICANON | LibcDefs.ECHO | LibcDefs.ECHONL);
}
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();
//System.out.println("New console");
}
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<String> 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);
}