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 System.loadLibrary("le"); 68 } 69 70 @Override 71 public void init() throws Exception { 72 super.init(); 73 74 // setAnsiSupported(Configuration.getBoolean(ANSI, true)); 75 setAnsiSupported(false); 76 77 // 78 // FIXME: Need a way to disable direct console and sysin detection muck 79 // 80 81 setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true)); 82 83 this.originalMode = getConsoleMode(); 84 setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code); 85 setEchoEnabled(false); 86 } 87 88 /** 89 * Restore the original terminal configuration, which can be used when 90 * shutting down the console reader. The ConsoleReader cannot be 91 * used after calling this method. 92 */ 93 @Override 94 public void restore() throws Exception { 95 // restore the old console mode 96 setConsoleMode(originalMode); 97 super.restore(); 98 } 99 100 @Override 101 public int getWidth() { 102 int w = getWindowsTerminalWidth(); 103 return w < 1 ? DEFAULT_WIDTH : w; 104 } 105 106 @Override 107 public int getHeight() { 108 int h = getWindowsTerminalHeight(); 109 return h < 1 ? DEFAULT_HEIGHT : h; 110 } 111 112 @Override 113 public void setEchoEnabled(final boolean enabled) { 114 // Must set these four modes at the same time to make it work fine. 115 if (enabled) { 116 setConsoleMode(getConsoleMode() | 117 ENABLE_ECHO_INPUT.code | 118 ENABLE_LINE_INPUT.code | 119 ENABLE_PROCESSED_INPUT.code | 120 ENABLE_WINDOW_INPUT.code); 121 } 122 else { 123 setConsoleMode(getConsoleMode() & 124 ~(ENABLE_LINE_INPUT.code | 125 ENABLE_ECHO_INPUT.code | 126 ENABLE_PROCESSED_INPUT.code | 127 ENABLE_WINDOW_INPUT.code)); 128 } 129 super.setEchoEnabled(enabled); 130 } 131 132 /** 133 * Whether or not to allow the use of the JNI console interaction. 134 */ 135 public void setDirectConsole(final boolean flag) { 136 this.directConsole = flag; 137 Log.debug("Direct console: ", flag); 138 } 139 140 /** 141 * Whether or not to allow the use of the JNI console interaction. 142 */ 143 public Boolean getDirectConsole() { 144 return directConsole; 145 } 146 147 148 @Override 149 public InputStream wrapInIfNeeded(InputStream in) throws IOException { 150 if (directConsole && isSystemIn(in)) { 151 return new InputStream() { 152 private byte[] buf = null; 153 int bufIdx = 0; 154 155 @Override 156 public int read() throws IOException { 157 while (buf == null || bufIdx == buf.length) { 158 buf = readConsoleInput(); 159 bufIdx = 0; 160 } 161 int c = buf[bufIdx] & 0xFF; 162 bufIdx++; 163 return c; 164 } 165 }; 166 } else { 167 return super.wrapInIfNeeded(in); 168 } 169 } 170 171 protected boolean isSystemIn(final InputStream in) throws IOException { 172 if (in == null) { 173 return false; 174 } 175 else if (in == System.in) { 176 return true; 177 } 178 else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) { 179 return true; 180 } 181 182 return false; 183 } 184 185 @Override 186 public String getOutputEncoding() { 187 int codepage = getConsoleOutputCodepage(); 188 //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html 189 String charsetMS = "ms" + codepage; 190 if (java.nio.charset.Charset.isSupported(charsetMS)) { 191 return charsetMS; 192 } 193 String charsetCP = "cp" + codepage; 194 if (java.nio.charset.Charset.isSupported(charsetCP)) { 195 return charsetCP; 196 } 197 Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")..."); 198 return super.getOutputEncoding(); 199 } 200 201 // 202 // Original code: 203 // 204 // private int getConsoleMode() { 205 // return WindowsSupport.getConsoleMode(); 206 // } 207 // 208 // private void setConsoleMode(int mode) { 209 // WindowsSupport.setConsoleMode(mode); 210 // } 211 // 212 // private byte[] readConsoleInput() { 213 // // XXX does how many events to read in one call matter? 214 // INPUT_RECORD[] events = null; 215 // try { 216 // events = WindowsSupport.readConsoleInput(1); 217 // } catch (IOException e) { 218 // Log.debug("read Windows console input error: ", e); 219 // } 220 // if (events == null) { 221 // return new byte[0]; 222 // } 223 // StringBuilder sb = new StringBuilder(); 224 // for (int i = 0; i < events.length; i++ ) { 225 // KEY_EVENT_RECORD keyEvent = events[i].keyEvent; 226 // //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); 227 // if (keyEvent.keyDown) { 228 // if (keyEvent.uchar > 0) { 229 // // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii> 230 // // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set 231 // final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED; 232 // // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed, 233 // // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors 234 // final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED; 235 // if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z')) 236 // && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) { 237 // sb.append('\u001B'); // ESC 238 // } 239 // 240 // sb.append(keyEvent.uchar); 241 // continue; 242 // } 243 // // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 244 // // just add support for basic editing keys (no control state, no numpad keys) 245 // String escapeSequence = null; 246 // switch (keyEvent.keyCode) { 247 // case 0x21: // VK_PRIOR PageUp 248 // escapeSequence = "\u001B[5~"; 249 // break; 250 // case 0x22: // VK_NEXT PageDown 251 // escapeSequence = "\u001B[6~"; 252 // break; 253 // case 0x23: // VK_END 254 // escapeSequence = "\u001B[4~"; 255 // break; 256 // case 0x24: // VK_HOME 257 // escapeSequence = "\u001B[1~"; 258 // break; 259 // case 0x25: // VK_LEFT 260 // escapeSequence = "\u001B[D"; 261 // break; 262 // case 0x26: // VK_UP 263 // escapeSequence = "\u001B[A"; 264 // break; 265 // case 0x27: // VK_RIGHT 266 // escapeSequence = "\u001B[C"; 267 // break; 268 // case 0x28: // VK_DOWN 269 // escapeSequence = "\u001B[B"; 270 // break; 271 // case 0x2D: // VK_INSERT 272 // escapeSequence = "\u001B[2~"; 273 // break; 274 // case 0x2E: // VK_DELETE 275 // escapeSequence = "\u001B[3~"; 276 // break; 277 // default: 278 // break; 279 // } 280 // if (escapeSequence != null) { 281 // for (int k = 0; k < keyEvent.repeatCount; k++) { 282 // sb.append(escapeSequence); 283 // } 284 // } 285 // } else { 286 // // key up event 287 // // support ALT+NumPad input method 288 // if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) { 289 // sb.append(keyEvent.uchar); 290 // } 291 // } 292 // } 293 // return sb.toString().getBytes(); 294 // } 295 // 296 // private int getConsoleOutputCodepage() { 297 // return Kernel32.GetConsoleOutputCP(); 298 // } 299 // 300 // private int getWindowsTerminalWidth() { 301 // return WindowsSupport.getWindowsTerminalWidth(); 302 // } 303 // 304 // private int getWindowsTerminalHeight() { 305 // return WindowsSupport.getWindowsTerminalHeight(); 306 // } 307 308 // 309 // Native Bits 310 // 311 private native int getConsoleMode(); 312 313 private native void setConsoleMode(int mode); 314 315 private byte[] readConsoleInput() { 316 KEY_EVENT_RECORD keyEvent = readKeyEvent(); 317 318 return convertKeys(keyEvent).getBytes(); 319 } 320 321 public static String convertKeys(KEY_EVENT_RECORD keyEvent) { 322 if (keyEvent == null) { 323 return ""; 324 } 325 326 StringBuilder sb = new StringBuilder(); 327 328 if (keyEvent.keyDown) { 329 if (keyEvent.uchar > 0) { 330 // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii> 331 // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set 332 final int altState = KEY_EVENT_RECORD.ALT_PRESSED; 333 // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed, 334 // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors 335 final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED; 336 337 boolean handled = false; 338 339 if ((keyEvent.controlKeyState & ctrlState) != 0) { 340 switch (keyEvent.keyCode) { 341 case 0x43: //Ctrl-C 342 sb.append("\003"); 343 handled = true; 344 break; 345 } 346 } 347 348 if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) { 349 switch (keyEvent.keyCode) { 350 case 0x09: //Shift-Tab 351 sb.append("\033\133\132"); 352 handled = true; 353 break; 354 } 355 } 356 357 if (!handled) { 358 if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z')) 359 && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) { 360 sb.append('\u001B'); // ESC 361 } 362 363 sb.append(keyEvent.uchar); 364 } 365 } else { 366 // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx 367 // just add support for basic editing keys (no control state, no numpad keys) 368 String escapeSequence = null; 369 switch (keyEvent.keyCode) { 370 case 0x21: // VK_PRIOR PageUp 371 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~"); 372 break; 373 case 0x22: // VK_NEXT PageDown 374 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~"); 375 break; 376 case 0x23: // VK_END 377 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~"); 378 break; 379 case 0x24: // VK_HOME 380 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~"); 381 break; 382 case 0x25: // VK_LEFT 383 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD"); 384 break; 385 case 0x26: // VK_UP 386 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA"); 387 break; 388 case 0x27: // VK_RIGHT 389 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC"); 390 break; 391 case 0x28: // VK_DOWN 392 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB"); 393 break; 394 case 0x2D: // VK_INSERT 395 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~"); 396 break; 397 case 0x2E: // VK_DELETE 398 escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~"); 399 break; 400 default: 401 break; 402 } 403 if (escapeSequence != null) { 404 for (int k = 0; k < keyEvent.repeatCount; k++) { 405 sb.append(escapeSequence); 406 } 407 } 408 } 409 } else { 410 // key up event 411 // support ALT+NumPad input method 412 if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) { 413 sb.append(keyEvent.uchar); 414 } 415 } 416 return sb.toString(); 417 } 418 419 private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) { 420 int controlNum = 1; 421 422 if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) { 423 controlNum += 1; 424 } 425 426 if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) { 427 controlNum += 2; 428 } 429 430 if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) { 431 controlNum += 4; 432 } 433 434 if (controlNum > 1) { 435 return String.format(withControlSequence, controlNum); 436 } else { 437 return noControlSequence; 438 } 439 } 440 441 private native KEY_EVENT_RECORD readKeyEvent(); 442 443 public static class KEY_EVENT_RECORD { 444 public final static int ALT_PRESSED = 0x3; 445 public final static int CTRL_PRESSED = 0xC; 446 public final static int SHIFT_PRESSED = 0x10; 447 public final boolean keyDown; 448 public final char uchar; 449 public final int controlKeyState; 450 public final int keyCode; 451 public final int repeatCount; 452 453 public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) { 454 this.keyDown = keyDown; 455 this.uchar = uchar; 456 this.controlKeyState = controlKeyState; 457 this.keyCode = keyCode; 458 this.repeatCount = repeatCount; 459 } 460 461 } 462 463 private native int getConsoleOutputCodepage(); 464 465 private native int getWindowsTerminalWidth(); 466 467 private native int getWindowsTerminalHeight(); 468 469 /** 470 * Console mode 471 * <p/> 472 * Constants copied <tt>wincon.h</tt>. 473 */ 474 public static enum ConsoleMode 475 { 476 /** 477 * The ReadFile or ReadConsole function returns only when a carriage return 478 * character is read. If this mode is disable, the functions return when one 479 * or more characters are available. 480 */ 481 ENABLE_LINE_INPUT(2), 482 483 /** 484 * Characters read by the ReadFile or ReadConsole function are written to 485 * the active screen buffer as they are read. This mode can be used only if 486 * the ENABLE_LINE_INPUT mode is also enabled. 487 */ 488 ENABLE_ECHO_INPUT(4), 489 490 /** 491 * CTRL+C is processed by the system and is not placed in the input buffer. 492 * If the input buffer is being read by ReadFile or ReadConsole, other 493 * control keys are processed by the system and are not returned in the 494 * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also 495 * enabled, backspace, carriage return, and linefeed characters are handled 496 * by the system. 497 */ 498 ENABLE_PROCESSED_INPUT(1), 499 500 /** 501 * User interactions that change the size of the console screen buffer are 502 * reported in the console's input buffee. Information about these events 503 * can be read from the input buffer by applications using 504 * theReadConsoleInput function, but not by those using ReadFile 505 * orReadConsole. 506 */ 507 ENABLE_WINDOW_INPUT(8), 508 509 /** 510 * If the mouse pointer is within the borders of the console window and the 511 * window has the keyboard focus, mouse events generated by mouse movement 512 * and button presses are placed in the input buffer. These events are 513 * discarded by ReadFile or ReadConsole, even when this mode is enabled. 514 */ 515 ENABLE_MOUSE_INPUT(16), 516 517 /** 518 * When enabled, text entered in a console window will be inserted at the 519 * current cursor location and all text following that location will not be 520 * overwritten. When disabled, all following text will be overwritten. An OR 521 * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS 522 * flag to enable this functionality. 523 */ 524 ENABLE_PROCESSED_OUTPUT(1), 525 526 /** 527 * This flag enables the user to use the mouse to select and edit text. To 528 * enable this option, use the OR to combine this flag with 529 * ENABLE_EXTENDED_FLAGS. 530 */ 531 ENABLE_WRAP_AT_EOL_OUTPUT(2),; 532 533 public final int code; 534 535 ConsoleMode(final int code) { 536 this.code = code; 537 } 538 } 539 540 }