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 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 * Terminal implementation for Microsoft Windows. Terminal initialization in 30 * {@link #init} is accomplished by extracting the 31 * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary 32 * directoy (determined by the setting of the <em>java.io.tmpdir</em> System 33 * property), loading the library, and then calling the Win32 APIs <a 34 * href="http://msdn.microsoft.com/library/default.asp? 35 * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and 36 * <a href="http://msdn.microsoft.com/library/default.asp? 37 * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to 38 * disable character echoing. 39 * <p/> 40 * <p> 41 * By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt 42 * to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper 43 * around {@link FileDescriptor#in}, and if so, will bypass the character reading to 44 * directly invoke the readc() method in the JNI library. This is so the class 45 * can read special keys (like arrow keys) which are otherwise inaccessible via 46 * the {@link System#in} stream. Using JNI reading can be bypassed by setting 47 * the <code>jline.WindowsTerminal.directConsole</code> system property 48 * to <code>false</code>. 49 * </p> 50 * 51 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 52 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> 53 * @since 2.0 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 // 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 // Native Bits 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 * Console mode 309 * <p/> 310 * Constants copied <tt>wincon.h</tt>. 311 */ 312 public static enum ConsoleMode 313 { 314 /** 315 * The ReadFile or ReadConsole function returns only when a carriage return 316 * character is read. If this mode is disable, the functions return when one 317 * or more characters are available. 318 */ 319 ENABLE_LINE_INPUT(2), 320 321 /** 322 * Characters read by the ReadFile or ReadConsole function are written to 323 * the active screen buffer as they are read. This mode can be used only if 324 * the ENABLE_LINE_INPUT mode is also enabled. 325 */ 326 ENABLE_ECHO_INPUT(4), 327 328 /** 329 * CTRL+C is processed by the system and is not placed in the input buffer. 330 * If the input buffer is being read by ReadFile or ReadConsole, other 331 * control keys are processed by the system and are not returned in the 332 * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also 333 * enabled, backspace, carriage return, and linefeed characters are handled 334 * by the system. 335 */ 336 ENABLE_PROCESSED_INPUT(1), 337 338 /** 339 * User interactions that change the size of the console screen buffer are 340 * reported in the console's input buffee. Information about these events 341 * can be read from the input buffer by applications using 342 * theReadConsoleInput function, but not by those using ReadFile 343 * orReadConsole. 344 */ 345 ENABLE_WINDOW_INPUT(8), 346 347 /** 348 * If the mouse pointer is within the borders of the console window and the 349 * window has the keyboard focus, mouse events generated by mouse movement 350 * and button presses are placed in the input buffer. These events are 351 * discarded by ReadFile or ReadConsole, even when this mode is enabled. 352 */ 353 ENABLE_MOUSE_INPUT(16), 354 355 /** 356 * When enabled, text entered in a console window will be inserted at the 357 * current cursor location and all text following that location will not be 358 * overwritten. When disabled, all following text will be overwritten. An OR 359 * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS 360 * flag to enable this functionality. 361 */ 362 ENABLE_PROCESSED_OUTPUT(1), 363 364 /** 365 * This flag enables the user to use the mouse to select and edit text. To 366 * enable this option, use the OR to combine this flag with 367 * ENABLE_EXTENDED_FLAGS. 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 }