1 /* 2 * Copyright (c) 2002-2012, the original author or authors. 3 * 4 * This software is distributable under the BSD license. See the terms of the 5 * BSD license in the documentation provided with this software. 6 * 7 * http://www.opensource.org/licenses/bsd-license.php 8 */ 9 package jdk.internal.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 jdk.internal.jline.internal.Configuration; 17 import jdk.internal.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 jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT; 23 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT; 24 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT; 25 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT; 26 27 /** 28 * Terminal implementation for Microsoft Windows. Terminal initialization in 29 * {@link #init} is accomplished by extracting the 30 * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary 31 * directoy (determined by the setting of the <em>java.io.tmpdir</em> System 32 * property), loading the library, and then calling the Win32 APIs <a 33 * href="http://msdn.microsoft.com/library/default.asp? 34 * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and 35 * <a href="http://msdn.microsoft.com/library/default.asp? 36 * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to 37 * disable character echoing. 38 * <p/> 39 * <p> 40 * By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt 41 * to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper 42 * around {@link FileDescriptor#in}, and if so, will bypass the character reading to 43 * directly invoke the readc() method in the JNI library. This is so the class 44 * can read special keys (like arrow keys) which are otherwise inaccessible via 45 * the {@link System#in} stream. Using JNI reading can be bypassed by setting 46 * the <code>jline.WindowsTerminal.directConsole</code> system property 47 * to <code>false</code>. 48 * </p> 49 * 50 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 51 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> 52 * @since 2.0 53 */ 54 public class WindowsTerminal 55 extends TerminalSupport 56 { 57 public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole"; 58 59 public static final String ANSI = WindowsTerminal.class.getName() + ".ansi"; 60 61 private boolean directConsole; 62 63 private int originalMode; 64 65 public WindowsTerminal() throws Exception { 66 super(true); 67 } 68 69 @Override 70 public void init() throws Exception { 71 super.init(); 72 73 // setAnsiSupported(Configuration.getBoolean(ANSI, true)); 74 setAnsiSupported(false); 75 76 // 77 // FIXME: Need a way to disable direct console and sysin detection muck 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 * Restore the original terminal configuration, which can be used when 89 * shutting down the console reader. The ConsoleReader cannot be 90 * used after calling this method. 91 */ 92 @Override 93 public void restore() throws Exception { 94 // restore the old console mode 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 // Must set these four modes at the same time to make it work fine. 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 * Whether or not to allow the use of the JNI console interaction. 133 */ 134 public void setDirectConsole(final boolean flag) { 135 this.directConsole = flag; 136 Log.debug("Direct console: ", flag); 137 } 138 139 /** 140 * Whether or not to allow the use of the JNI console interaction. 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 //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html 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 // Original code: 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 // // XXX does how many events to read in one call matter? 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 // //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); 226 // if (keyEvent.keyDown) { 227 // if (keyEvent.uchar > 0) { 228 // // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii> 229 // // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set 230 // final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED; 231 // // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed, 232 // // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors 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'); // ESC 237 // } 238 // 239 // sb.append(keyEvent.uchar); 240 // continue; 241 // } 242 // // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 243 // // just add support for basic editing keys (no control state, no numpad keys) 244 // String escapeSequence = null; 245 // switch (keyEvent.keyCode) { 246 // case 0x21: // VK_PRIOR PageUp 247 // escapeSequence = "\u001B[5~"; 248 // break; 249 // case 0x22: // VK_NEXT PageDown 250 // escapeSequence = "\u001B[6~"; 251 // break; 252 // case 0x23: // VK_END 253 // escapeSequence = "\u001B[4~"; 254 // break; 255 // case 0x24: // VK_HOME 256 // escapeSequence = "\u001B[1~"; 257 // break; 258 // case 0x25: // VK_LEFT 259 // escapeSequence = "\u001B[D"; 260 // break; 261 // case 0x26: // VK_UP 262 // escapeSequence = "\u001B[A"; 263 // break; 264 // case 0x27: // VK_RIGHT 265 // escapeSequence = "\u001B[C"; 266 // break; 267 // case 0x28: // VK_DOWN 268 // escapeSequence = "\u001B[B"; 269 // break; 270 // case 0x2D: // VK_INSERT 271 // escapeSequence = "\u001B[2~"; 272 // break; 273 // case 0x2E: // VK_DELETE 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 // // key up event 286 // // support ALT+NumPad input method 287 // if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) { 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 // Native Bits 309 // 310 static { 311 System.loadLibrary("le"); 312 initIDs(); 313 } 314 315 private static native void initIDs(); 316 317 private native int getConsoleMode(); 318 319 private native void setConsoleMode(int mode); 320 321 private byte[] readConsoleInput() { 322 KEY_EVENT_RECORD keyEvent = readKeyEvent(); 323 324 return convertKeys(keyEvent).getBytes(); 325 } 326 327 public static String convertKeys(KEY_EVENT_RECORD keyEvent) { 328 if (keyEvent == null) { 329 return ""; 330 } 331 332 StringBuilder sb = new StringBuilder(); 333 334 if (keyEvent.keyDown) { 335 if (keyEvent.uchar > 0) { 336 // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii> 337 // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set 338 final int altState = KEY_EVENT_RECORD.ALT_PRESSED; 339 // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed, 340 // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors 341 final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED; 342 343 boolean handled = false; 344 345 if ((keyEvent.controlKeyState & ctrlState) != 0) { 346 switch (keyEvent.keyCode) { 347 case 0x43: //Ctrl-C 348 sb.append("\003"); 349 handled = true; 350 break; 351 } 352 } 353 354 if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) { 355 switch (keyEvent.keyCode) { 356 case 0x09: //Shift-Tab 357 sb.append("\033\133\132"); 358 handled = true; 359 break; 360 } 361 } 362 363 if (!handled) { 364 if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z')) 365 && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) { 366 sb.append('\u001B'); // ESC 367 } 368 369 sb.append(keyEvent.uchar); 370 } 371 } else { 372 // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 373 // just add support for basic editing keys (no control state, no numpad keys) 374 String escapeSequence = null; 375 switch (keyEvent.keyCode) { 376 case 0x21: // VK_PRIOR PageUp 377 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~"); 378 break; 379 case 0x22: // VK_NEXT PageDown 380 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~"); 381 break; 382 case 0x23: // VK_END 383 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~"); 384 break; 385 case 0x24: // VK_HOME 386 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~"); 387 break; 388 case 0x25: // VK_LEFT 389 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD"); 390 break; 391 case 0x26: // VK_UP 392 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA"); 393 break; 394 case 0x27: // VK_RIGHT 395 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC"); 396 break; 397 case 0x28: // VK_DOWN 398 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB"); 399 break; 400 case 0x2D: // VK_INSERT 401 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~"); 402 break; 403 case 0x2E: // VK_DELETE 404 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~"); 405 break; 406 case 0x70: // VK_F1 407 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOP", "\u001BO%dP"); 408 break; 409 case 0x71: // VK_F2 410 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOQ", "\u001BO%dQ"); 411 break; 412 case 0x72: // VK_F3 413 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOR", "\u001BO%dR"); 414 break; 415 case 0x73: // VK_F4 416 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOS", "\u001BO%dS"); 417 break; 418 case 0x74: // VK_F5 419 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[15~", "\u001B[15;%d~"); 420 break; 421 case 0x75: // VK_F6 422 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[17~", "\u001B[17;%d~"); 423 break; 424 case 0x76: // VK_F7 425 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[18~", "\u001B[18;%d~"); 426 break; 427 case 0x77: // VK_F8 428 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[19~", "\u001B[19;%d~"); 429 break; 430 case 0x78: // VK_F9 431 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[20~", "\u001B[20;%d~"); 432 break; 433 case 0x79: // VK_F10 434 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[21~", "\u001B[21;%d~"); 435 break; 436 case 0x7A: // VK_F11 437 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[23~", "\u001B[23;%d~"); 438 break; 439 case 0x7B: // VK_F12 440 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[24~", "\u001B[24;%d~"); 441 break; 442 default: 443 break; 444 } 445 if (escapeSequence != null) { 446 for (int k = 0; k < keyEvent.repeatCount; k++) { 447 sb.append(escapeSequence); 448 } 449 } 450 } 451 } else { 452 // key up event 453 // support ALT+NumPad input method 454 if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) { 455 sb.append(keyEvent.uchar); 456 } 457 } 458 return sb.toString(); 459 } 460 461 private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) { 462 int controlNum = 1; 463 464 if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) { 465 controlNum += 1; 466 } 467 468 if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) { 469 controlNum += 2; 470 } 471 472 if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) { 473 controlNum += 4; 474 } 475 476 if (controlNum > 1) { 477 return String.format(withControlSequence, controlNum); 478 } else { 479 return noControlSequence; 480 } 481 } 482 483 private native KEY_EVENT_RECORD readKeyEvent(); 484 485 public static class KEY_EVENT_RECORD { 486 public final static int ALT_PRESSED = 0x3; 487 public final static int CTRL_PRESSED = 0xC; 488 public final static int SHIFT_PRESSED = 0x10; 489 public final boolean keyDown; 490 public final char uchar; 491 public final int controlKeyState; 492 public final int keyCode; 493 public final int repeatCount; 494 495 public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) { 496 this.keyDown = keyDown; 497 this.uchar = uchar; 498 this.controlKeyState = controlKeyState; 499 this.keyCode = keyCode; 500 this.repeatCount = repeatCount; 501 } 502 503 } 504 505 private native int getConsoleOutputCodepage(); 506 507 private native int getWindowsTerminalWidth(); 508 509 private native int getWindowsTerminalHeight(); 510 511 /** 512 * Console mode 513 * <p/> 514 * Constants copied <tt>wincon.h</tt>. 515 */ 516 public static enum ConsoleMode 517 { 518 /** 519 * The ReadFile or ReadConsole function returns only when a carriage return 520 * character is read. If this mode is disable, the functions return when one 521 * or more characters are available. 522 */ 523 ENABLE_LINE_INPUT(2), 524 525 /** 526 * Characters read by the ReadFile or ReadConsole function are written to 527 * the active screen buffer as they are read. This mode can be used only if 528 * the ENABLE_LINE_INPUT mode is also enabled. 529 */ 530 ENABLE_ECHO_INPUT(4), 531 532 /** 533 * CTRL+C is processed by the system and is not placed in the input buffer. 534 * If the input buffer is being read by ReadFile or ReadConsole, other 535 * control keys are processed by the system and are not returned in the 536 * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also 537 * enabled, backspace, carriage return, and linefeed characters are handled 538 * by the system. 539 */ 540 ENABLE_PROCESSED_INPUT(1), 541 542 /** 543 * User interactions that change the size of the console screen buffer are 544 * reported in the console's input buffee. Information about these events 545 * can be read from the input buffer by applications using 546 * theReadConsoleInput function, but not by those using ReadFile 547 * orReadConsole. 548 */ 549 ENABLE_WINDOW_INPUT(8), 550 551 /** 552 * If the mouse pointer is within the borders of the console window and the 553 * window has the keyboard focus, mouse events generated by mouse movement 554 * and button presses are placed in the input buffer. These events are 555 * discarded by ReadFile or ReadConsole, even when this mode is enabled. 556 */ 557 ENABLE_MOUSE_INPUT(16), 558 559 /** 560 * When enabled, text entered in a console window will be inserted at the 561 * current cursor location and all text following that location will not be 562 * overwritten. When disabled, all following text will be overwritten. An OR 563 * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS 564 * flag to enable this functionality. 565 */ 566 ENABLE_PROCESSED_OUTPUT(1), 567 568 /** 569 * This flag enables the user to use the mouse to select and edit text. To 570 * enable this option, use the OR to combine this flag with 571 * ENABLE_EXTENDED_FLAGS. 572 */ 573 ENABLE_WRAP_AT_EOL_OUTPUT(2),; 574 575 public final int code; 576 577 ConsoleMode(final int code) { 578 this.code = code; 579 } 580 } 581 582 }