1
2
3
4
5
6
7
8
9 package jline;
10
11 import java.io.FileDescriptor;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15
16 import jline.internal.Configuration;
17 import jline.internal.Log;
18 import org.fusesource.jansi.internal.WindowsSupport;
19 import org.fusesource.jansi.internal.Kernel32;
20 import static org.fusesource.jansi.internal.Kernel32.*;
21
22 import static jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT;
23 import static jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT;
24 import static jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT;
25 import static jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT;
26 import static jline.internal.Preconditions.checkNotNull;
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 public class WindowsTerminal
56 extends TerminalSupport
57 {
58 public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
59
60 public static final String ANSI = WindowsTerminal.class.getName() + ".ansi";
61
62 private boolean directConsole;
63
64 private int originalMode;
65
66 public WindowsTerminal() throws Exception {
67 super(true);
68 }
69
70 @Override
71 public void init() throws Exception {
72 super.init();
73
74 setAnsiSupported(Configuration.getBoolean(ANSI, true));
75
76
77
78
79
80 setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true));
81
82 this.originalMode = getConsoleMode();
83 setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code);
84 setEchoEnabled(false);
85 }
86
87
88
89
90
91
92 @Override
93 public void restore() throws Exception {
94
95 setConsoleMode(originalMode);
96 super.restore();
97 }
98
99 @Override
100 public int getWidth() {
101 int w = getWindowsTerminalWidth();
102 return w < 1 ? DEFAULT_WIDTH : w;
103 }
104
105 @Override
106 public int getHeight() {
107 int h = getWindowsTerminalHeight();
108 return h < 1 ? DEFAULT_HEIGHT : h;
109 }
110
111 @Override
112 public void setEchoEnabled(final boolean enabled) {
113
114 if (enabled) {
115 setConsoleMode(getConsoleMode() |
116 ENABLE_ECHO_INPUT.code |
117 ENABLE_LINE_INPUT.code |
118 ENABLE_PROCESSED_INPUT.code |
119 ENABLE_WINDOW_INPUT.code);
120 }
121 else {
122 setConsoleMode(getConsoleMode() &
123 ~(ENABLE_LINE_INPUT.code |
124 ENABLE_ECHO_INPUT.code |
125 ENABLE_PROCESSED_INPUT.code |
126 ENABLE_WINDOW_INPUT.code));
127 }
128 super.setEchoEnabled(enabled);
129 }
130
131
132
133
134 public void setDirectConsole(final boolean flag) {
135 this.directConsole = flag;
136 Log.debug("Direct console: ", flag);
137 }
138
139
140
141
142 public Boolean getDirectConsole() {
143 return directConsole;
144 }
145
146
147 @Override
148 public InputStream wrapInIfNeeded(InputStream in) throws IOException {
149 if (directConsole && isSystemIn(in)) {
150 return new InputStream() {
151 private byte[] buf = null;
152 int bufIdx = 0;
153
154 @Override
155 public int read() throws IOException {
156 while (buf == null || bufIdx == buf.length) {
157 buf = readConsoleInput();
158 bufIdx = 0;
159 }
160 int c = buf[bufIdx] & 0xFF;
161 bufIdx++;
162 return c;
163 }
164 };
165 } else {
166 return super.wrapInIfNeeded(in);
167 }
168 }
169
170 protected boolean isSystemIn(final InputStream in) throws IOException {
171 if (in == null) {
172 return false;
173 }
174 else if (in == System.in) {
175 return true;
176 }
177 else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
178 return true;
179 }
180
181 return false;
182 }
183
184 @Override
185 public String getOutputEncoding() {
186 int codepage = getConsoleOutputCodepage();
187
188 String charsetMS = "ms" + codepage;
189 if (java.nio.charset.Charset.isSupported(charsetMS)) {
190 return charsetMS;
191 }
192 String charsetCP = "cp" + codepage;
193 if (java.nio.charset.Charset.isSupported(charsetCP)) {
194 return charsetCP;
195 }
196 Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")...");
197 return super.getOutputEncoding();
198 }
199
200
201
202
203 private int getConsoleMode() {
204 return WindowsSupport.getConsoleMode();
205 }
206
207 private void setConsoleMode(int mode) {
208 WindowsSupport.setConsoleMode(mode);
209 }
210
211 private byte[] readConsoleInput() {
212
213 INPUT_RECORD[] events = null;
214 try {
215 events = WindowsSupport.readConsoleInput(1);
216 } catch (IOException e) {
217 Log.debug("read Windows console input error: ", e);
218 }
219 if (events == null) {
220 return new byte[0];
221 }
222 StringBuilder sb = new StringBuilder();
223 for (int i = 0; i < events.length; i++ ) {
224 KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
225
226 if (keyEvent.keyDown) {
227 if (keyEvent.uchar > 0) {
228
229
230 final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
231
232
233 final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
234 if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
235 && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
236 sb.append('\u001B');
237 }
238
239 sb.append(keyEvent.uchar);
240 continue;
241 }
242
243
244 String escapeSequence = null;
245 switch (keyEvent.keyCode) {
246 case 0x21:
247 escapeSequence = "\u001B[5~";
248 break;
249 case 0x22:
250 escapeSequence = "\u001B[6~";
251 break;
252 case 0x23:
253 escapeSequence = "\u001B[4~";
254 break;
255 case 0x24:
256 escapeSequence = "\u001B[1~";
257 break;
258 case 0x25:
259 escapeSequence = "\u001B[D";
260 break;
261 case 0x26:
262 escapeSequence = "\u001B[A";
263 break;
264 case 0x27:
265 escapeSequence = "\u001B[C";
266 break;
267 case 0x28:
268 escapeSequence = "\u001B[B";
269 break;
270 case 0x2D:
271 escapeSequence = "\u001B[2~";
272 break;
273 case 0x2E:
274 escapeSequence = "\u001B[3~";
275 break;
276 default:
277 break;
278 }
279 if (escapeSequence != null) {
280 for (int k = 0; k < keyEvent.repeatCount; k++) {
281 sb.append(escapeSequence);
282 }
283 }
284 } else {
285
286
287 if (keyEvent.keyCode == 0x12
288 sb.append(keyEvent.uchar);
289 }
290 }
291 }
292 return sb.toString().getBytes();
293 }
294
295 private int getConsoleOutputCodepage() {
296 return Kernel32.GetConsoleOutputCP();
297 }
298
299 private int getWindowsTerminalWidth() {
300 return WindowsSupport.getWindowsTerminalWidth();
301 }
302
303 private int getWindowsTerminalHeight() {
304 return WindowsSupport.getWindowsTerminalHeight();
305 }
306
307
308
309
310
311
312 public static enum ConsoleMode
313 {
314
315
316
317
318
319 ENABLE_LINE_INPUT(2),
320
321
322
323
324
325
326 ENABLE_ECHO_INPUT(4),
327
328
329
330
331
332
333
334
335
336 ENABLE_PROCESSED_INPUT(1),
337
338
339
340
341
342
343
344
345 ENABLE_WINDOW_INPUT(8),
346
347
348
349
350
351
352
353 ENABLE_MOUSE_INPUT(16),
354
355
356
357
358
359
360
361
362 ENABLE_PROCESSED_OUTPUT(1),
363
364
365
366
367
368
369 ENABLE_WRAP_AT_EOL_OUTPUT(2),;
370
371 public final int code;
372
373 ConsoleMode(final int code) {
374 this.code = code;
375 }
376 }
377
378 }