001 /* 002 * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. 003 * 004 * This software is distributable under the BSD license. See the terms of the 005 * BSD license in the documentation provided with this software. 006 */ 007 package jline; 008 009 import java.io.*; 010 011 /** 012 * <p> 013 * Terminal implementation for Microsoft Windows. Terminal initialization in 014 * {@link #initializeTerminal} is accomplished by extracting the 015 * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary 016 * directoy (determined by the setting of the <em>java.io.tmpdir</em> System 017 * property), loading the library, and then calling the Win32 APIs <a 018 * href="http://msdn.microsoft.com/library/default.asp? 019 * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and 020 * <a href="http://msdn.microsoft.com/library/default.asp? 021 * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to 022 * disable character echoing. 023 * </p> 024 * 025 * <p> 026 * By default, the {@link #readCharacter} method will attempt to test to see if 027 * the specified {@link InputStream} is {@link System#in} or a wrapper around 028 * {@link FileDescriptor#in}, and if so, will bypass the character reading to 029 * directly invoke the readc() method in the JNI library. This is so the class 030 * can read special keys (like arrow keys) which are otherwise inaccessible via 031 * the {@link System#in} stream. Using JNI reading can be bypassed by setting 032 * the <code>jline.WindowsTerminal.disableDirectConsole</code> system property 033 * to <code>true</code>. 034 * </p> 035 * 036 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 037 */ 038 public class WindowsTerminal extends Terminal { 039 // constants copied from wincon.h 040 041 /** 042 * The ReadFile or ReadConsole function returns only when a carriage return 043 * character is read. If this mode is disable, the functions return when one 044 * or more characters are available. 045 */ 046 private static final int ENABLE_LINE_INPUT = 2; 047 048 /** 049 * Characters read by the ReadFile or ReadConsole function are written to 050 * the active screen buffer as they are read. This mode can be used only if 051 * the ENABLE_LINE_INPUT mode is also enabled. 052 */ 053 private static final int ENABLE_ECHO_INPUT = 4; 054 055 /** 056 * CTRL+C is processed by the system and is not placed in the input buffer. 057 * If the input buffer is being read by ReadFile or ReadConsole, other 058 * control keys are processed by the system and are not returned in the 059 * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also 060 * enabled, backspace, carriage return, and linefeed characters are handled 061 * by the system. 062 */ 063 private static final int ENABLE_PROCESSED_INPUT = 1; 064 065 /** 066 * User interactions that change the size of the console screen buffer are 067 * reported in the console's input buffee. Information about these events 068 * can be read from the input buffer by applications using 069 * theReadConsoleInput function, but not by those using ReadFile 070 * orReadConsole. 071 */ 072 private static final int ENABLE_WINDOW_INPUT = 8; 073 074 /** 075 * If the mouse pointer is within the borders of the console window and the 076 * window has the keyboard focus, mouse events generated by mouse movement 077 * and button presses are placed in the input buffer. These events are 078 * discarded by ReadFile or ReadConsole, even when this mode is enabled. 079 */ 080 private static final int ENABLE_MOUSE_INPUT = 16; 081 082 /** 083 * When enabled, text entered in a console window will be inserted at the 084 * current cursor location and all text following that location will not be 085 * overwritten. When disabled, all following text will be overwritten. An OR 086 * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS 087 * flag to enable this functionality. 088 */ 089 private static final int ENABLE_PROCESSED_OUTPUT = 1; 090 091 /** 092 * This flag enables the user to use the mouse to select and edit text. To 093 * enable this option, use the OR to combine this flag with 094 * ENABLE_EXTENDED_FLAGS. 095 */ 096 private static final int ENABLE_WRAP_AT_EOL_OUTPUT = 2; 097 098 /** 099 * On windows terminals, this character indicates that a 'special' key has 100 * been pressed. This means that a key such as an arrow key, or delete, or 101 * home, etc. will be indicated by the next character. 102 */ 103 public static final int SPECIAL_KEY_INDICATOR = 224; 104 105 /** 106 * On windows terminals, this character indicates that a special key on the 107 * number pad has been pressed. 108 */ 109 public static final int NUMPAD_KEY_INDICATOR = 0; 110 111 /** 112 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, 113 * this character indicates an left arrow key press. 114 */ 115 public static final int LEFT_ARROW_KEY = 75; 116 117 /** 118 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 119 * this character indicates an 120 * right arrow key press. 121 */ 122 public static final int RIGHT_ARROW_KEY = 77; 123 124 /** 125 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 126 * this character indicates an up 127 * arrow key press. 128 */ 129 public static final int UP_ARROW_KEY = 72; 130 131 /** 132 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 133 * this character indicates an 134 * down arrow key press. 135 */ 136 public static final int DOWN_ARROW_KEY = 80; 137 138 /** 139 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 140 * this character indicates that 141 * the delete key was pressed. 142 */ 143 public static final int DELETE_KEY = 83; 144 145 /** 146 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 147 * this character indicates that 148 * the home key was pressed. 149 */ 150 public static final int HOME_KEY = 71; 151 152 /** 153 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 154 * this character indicates that 155 * the end key was pressed. 156 */ 157 public static final char END_KEY = 79; 158 159 /** 160 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 161 * this character indicates that 162 * the page up key was pressed. 163 */ 164 public static final char PAGE_UP_KEY = 73; 165 166 /** 167 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 168 * this character indicates that 169 * the page down key was pressed. 170 */ 171 public static final char PAGE_DOWN_KEY = 81; 172 173 /** 174 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR 175 * this character indicates that 176 * the insert key was pressed. 177 */ 178 public static final char INSERT_KEY = 82; 179 180 /** 181 * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, 182 * this character indicates that the escape key was pressed. 183 */ 184 public static final char ESCAPE_KEY = 0; 185 186 private Boolean directConsole; 187 188 private boolean echoEnabled; 189 190 public WindowsTerminal() { 191 String dir = System.getProperty("jline.WindowsTerminal.directConsole"); 192 193 if ("true".equals(dir)) { 194 directConsole = Boolean.TRUE; 195 } else if ("false".equals(dir)) { 196 directConsole = Boolean.FALSE; 197 } 198 } 199 200 private native int getConsoleMode(); 201 202 private native void setConsoleMode(final int mode); 203 204 private native int readByte(); 205 206 private native int getWindowsTerminalWidth(); 207 208 private native int getWindowsTerminalHeight(); 209 210 public int readCharacter(final InputStream in) throws IOException { 211 // if we can detect that we are directly wrapping the system 212 // input, then bypass the input stream and read directly (which 213 // allows us to access otherwise unreadable strokes, such as 214 // the arrow keys) 215 if (directConsole == Boolean.FALSE) { 216 return super.readCharacter(in); 217 } else if ((directConsole == Boolean.TRUE) 218 || ((in == System.in) || (in instanceof FileInputStream 219 && (((FileInputStream) in).getFD() == FileDescriptor.in)))) { 220 return readByte(); 221 } else { 222 return super.readCharacter(in); 223 } 224 } 225 226 public void initializeTerminal() throws Exception { 227 loadLibrary("jline"); 228 229 final int originalMode = getConsoleMode(); 230 231 setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT); 232 233 // set the console to raw mode 234 int newMode = originalMode 235 & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT 236 | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); 237 echoEnabled = false; 238 setConsoleMode(newMode); 239 240 // at exit, restore the original tty configuration (for JDK 1.3+) 241 try { 242 Runtime.getRuntime().addShutdownHook(new Thread() { 243 public void start() { 244 // restore the old console mode 245 setConsoleMode(originalMode); 246 } 247 }); 248 } catch (AbstractMethodError ame) { 249 // JDK 1.3+ only method. Bummer. 250 consumeException(ame); 251 } 252 } 253 254 private void loadLibrary(final String name) throws IOException { 255 // store the DLL in the temporary directory for the System 256 String version = getClass().getPackage().getImplementationVersion(); 257 258 if (version == null) { 259 version = ""; 260 } 261 262 version = version.replace('.', '_'); 263 264 File f = new File(System.getProperty("java.io.tmpdir"), name + "_" 265 + version + ".dll"); 266 boolean exists = f.isFile(); // check if it already exists 267 268 // extract the embedded jline.dll file from the jar and save 269 // it to the current directory 270 int bits = 32; 271 272 // check for 64-bit systems and use to appropriate DLL 273 if (System.getProperty("os.arch").indexOf("64") != -1) 274 bits = 64; 275 276 InputStream in = new BufferedInputStream(getClass() 277 .getResourceAsStream(name + bits + ".dll")); 278 279 try { 280 OutputStream fout = new BufferedOutputStream( 281 new FileOutputStream(f)); 282 byte[] bytes = new byte[1024 * 10]; 283 284 for (int n = 0; n != -1; n = in.read(bytes)) { 285 fout.write(bytes, 0, n); 286 } 287 288 fout.close(); 289 } catch (IOException ioe) { 290 // We might get an IOException trying to overwrite an existing 291 // jline.dll file if there is another process using the DLL. 292 // If this happens, ignore errors. 293 if (!exists) { 294 throw ioe; 295 } 296 } 297 298 // try to clean up the DLL after the JVM exits 299 f.deleteOnExit(); 300 301 // now actually load the DLL 302 System.load(f.getAbsolutePath()); 303 } 304 305 public int readVirtualKey(InputStream in) throws IOException { 306 int indicator = readCharacter(in); 307 308 // in Windows terminals, arrow keys are represented by 309 // a sequence of 2 characters. E.g., the up arrow 310 // key yields 224, 72 311 if (indicator == SPECIAL_KEY_INDICATOR 312 || indicator == NUMPAD_KEY_INDICATOR) { 313 int key = readCharacter(in); 314 315 switch (key) { 316 case UP_ARROW_KEY: 317 return CTRL_P; // translate UP -> CTRL-P 318 case LEFT_ARROW_KEY: 319 return CTRL_B; // translate LEFT -> CTRL-B 320 case RIGHT_ARROW_KEY: 321 return CTRL_F; // translate RIGHT -> CTRL-F 322 case DOWN_ARROW_KEY: 323 return CTRL_N; // translate DOWN -> CTRL-N 324 case DELETE_KEY: 325 return CTRL_QM; // translate DELETE -> CTRL-? 326 case HOME_KEY: 327 return CTRL_A; 328 case END_KEY: 329 return CTRL_E; 330 case PAGE_UP_KEY: 331 return CTRL_K; 332 case PAGE_DOWN_KEY: 333 return CTRL_L; 334 case ESCAPE_KEY: 335 return CTRL_OB; // translate ESCAPE -> CTRL-[ 336 case INSERT_KEY: 337 return CTRL_C; 338 default: 339 return 0; 340 } 341 } else { 342 return indicator; 343 } 344 } 345 346 public boolean isSupported() { 347 return true; 348 } 349 350 /** 351 * Windows doesn't support ANSI codes by default; disable them. 352 */ 353 public boolean isANSISupported() { 354 return false; 355 } 356 357 public boolean getEcho() { 358 return false; 359 } 360 361 /** 362 * Unsupported; return the default. 363 * 364 * @see Terminal#getTerminalWidth 365 */ 366 public int getTerminalWidth() { 367 return getWindowsTerminalWidth(); 368 } 369 370 /** 371 * Unsupported; return the default. 372 * 373 * @see Terminal#getTerminalHeight 374 */ 375 public int getTerminalHeight() { 376 return getWindowsTerminalHeight(); 377 } 378 379 /** 380 * No-op for exceptions we want to silently consume. 381 */ 382 private void consumeException(final Throwable e) { 383 } 384 385 /** 386 * Whether or not to allow the use of the JNI console interaction. 387 */ 388 public void setDirectConsole(Boolean directConsole) { 389 this.directConsole = directConsole; 390 } 391 392 /** 393 * Whether or not to allow the use of the JNI console interaction. 394 */ 395 public Boolean getDirectConsole() { 396 return this.directConsole; 397 } 398 399 public synchronized boolean isEchoEnabled() { 400 return echoEnabled; 401 } 402 403 public synchronized void enableEcho() { 404 // Must set these four modes at the same time to make it work fine. 405 setConsoleMode(getConsoleMode() | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT 406 | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); 407 echoEnabled = true; 408 } 409 410 public synchronized void disableEcho() { 411 // Must set these four modes at the same time to make it work fine. 412 setConsoleMode(getConsoleMode() 413 & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT 414 | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT)); 415 echoEnabled = true; 416 } 417 418 public InputStream getDefaultBindings() { 419 return getClass().getResourceAsStream("windowsbindings.properties"); 420 } 421 }