001 /* 002 * Copyright (c) 2002-2006, 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 014 * in {@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> 017 * System property), loading the library, and then calling the Win32 APIs 018 * <a href="http://msdn.microsoft.com/library/default.asp? 019 * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> 020 * and 021 * <a href="http://msdn.microsoft.com/library/default.asp? 022 * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> 023 * to disable character echoing. 024 * </p> 025 * 026 * <p> 027 * By default, the {@link #readCharacter} method will attempt to test 028 * to see if the specified {@link InputStream} is {@link System#in} 029 * or a wrapper around {@link FileDescriptor#in}, and if so, will 030 * bypass the character reading to directly invoke the 031 * readc() method in the JNI library. This is so the class can 032 * read special keys (like arrow keys) which are otherwise 033 * inaccessible via the {@link System#in} stream. Using JNI 034 * reading can be bypassed by setting the 035 * <code>jline.WindowsTerminal.disableDirectConsole</code> system 036 * property to <code>true</code>. 037 * </p> 038 * 039 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 040 */ 041 public class WindowsTerminal extends Terminal { 042 // constants copied from wincon.h 043 044 /** 045 * The ReadFile or ReadConsole function returns only when 046 * a carriage return character is read. If this mode is disable, 047 * the functions return when one or more characters are 048 * available. 049 */ 050 private static final int ENABLE_LINE_INPUT = 2; 051 052 /** 053 * Characters read by the ReadFile or ReadConsole function 054 * are written to the active screen buffer as they are read. 055 * This mode can be used only if the ENABLE_LINE_INPUT mode 056 * is also enabled. 057 */ 058 private static final int ENABLE_ECHO_INPUT = 4; 059 060 /** 061 * CTRL+C is processed by the system and is not placed 062 * in the input buffer. If the input buffer is being read 063 * by ReadFile or ReadConsole, other control keys are processed 064 * by the system and are not returned in the ReadFile or ReadConsole 065 * buffer. If the ENABLE_LINE_INPUT mode is also enabled, 066 * backspace, carriage return, and linefeed characters are 067 * handled by the system. 068 */ 069 private static final int ENABLE_PROCESSED_INPUT = 1; 070 071 /** 072 * User interactions that change the size of the console 073 * screen buffer are reported in the console's input buffee. 074 * Information about these events can be read from the input 075 * buffer by applications using theReadConsoleInput function, 076 * but not by those using ReadFile orReadConsole. 077 */ 078 private static final int ENABLE_WINDOW_INPUT = 8; 079 080 /** 081 * If the mouse pointer is within the borders of the console 082 * window and the window has the keyboard focus, mouse events 083 * generated by mouse movement and button presses are placed 084 * in the input buffer. These events are discarded by ReadFile 085 * or ReadConsole, even when this mode is enabled. 086 */ 087 private static final int ENABLE_MOUSE_INPUT = 16; 088 089 /** 090 * When enabled, text entered in a console window will 091 * be inserted at the current cursor location and all text 092 * following that location will not be overwritten. When disabled, 093 * all following text will be overwritten. An OR operation 094 * must be performed with this flag and the ENABLE_EXTENDED_FLAGS 095 * flag to enable this functionality. 096 */ 097 private static final int ENABLE_PROCESSED_OUTPUT = 1; 098 099 /** 100 * This flag enables the user to use the mouse to select 101 * and edit text. To enable this option, use the OR to combine 102 * this flag with ENABLE_EXTENDED_FLAGS. 103 */ 104 private static final int ENABLE_WRAP_AT_EOL_OUTPUT = 2; 105 private Boolean directConsole; 106 107 public WindowsTerminal() { 108 String dir = System.getProperty("jline.WindowsTerminal.directConsole"); 109 110 if ("true".equals(dir)) { 111 directConsole = Boolean.TRUE; 112 } else if ("false".equals(dir)) { 113 directConsole = Boolean.FALSE; 114 } 115 } 116 117 private native int getConsoleMode(); 118 119 private native void setConsoleMode(final int mode); 120 121 private native int readByte(); 122 123 private native int getWindowsTerminalWidth(); 124 125 private native int getWindowsTerminalHeight(); 126 127 public int readCharacter(final InputStream in) throws IOException { 128 // if we can detect that we are directly wrapping the system 129 // input, then bypass the input stream and read directly (which 130 // allows us to access otherwise unreadable strokes, such as 131 // the arrow keys) 132 if (directConsole == Boolean.FALSE) { 133 return super.readCharacter(in); 134 } else if ((directConsole == Boolean.TRUE) 135 || ((in == System.in) || (in instanceof FileInputStream 136 && (((FileInputStream) in).getFD() == FileDescriptor.in)))) { 137 return readByte(); 138 } else { 139 return super.readCharacter(in); 140 } 141 } 142 143 public void initializeTerminal() throws Exception { 144 loadLibrary("jline"); 145 146 final int originalMode = getConsoleMode(); 147 148 setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT); 149 150 // set the console to raw mode 151 int newMode = 152 originalMode 153 & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT 154 | ENABLE_WINDOW_INPUT); 155 setConsoleMode(newMode); 156 157 // at exit, restore the original tty configuration (for JDK 1.3+) 158 try { 159 Runtime.getRuntime().addShutdownHook(new Thread() { 160 public void start() { 161 // restore the old console mode 162 setConsoleMode(originalMode); 163 } 164 }); 165 } catch (AbstractMethodError ame) { 166 // JDK 1.3+ only method. Bummer. 167 consumeException(ame); 168 } 169 } 170 171 private void loadLibrary(final String name) throws IOException { 172 // store the DLL in the temporary directory for the System 173 String version = getClass().getPackage().getImplementationVersion(); 174 175 if (version == null) { 176 version = ""; 177 } 178 179 version = version.replace('.', '_'); 180 181 File f = 182 new File(System.getProperty("java.io.tmpdir"), 183 name + "_" + version + ".dll"); 184 boolean exists = f.isFile(); // check if it already exists 185 186 // extract the embedded jline.dll file from the jar and save 187 // it to the current directory 188 InputStream in = 189 new BufferedInputStream(getClass().getResourceAsStream(name 190 + ".dll")); 191 192 try { 193 OutputStream fout = 194 new BufferedOutputStream(new FileOutputStream(f)); 195 byte[] bytes = new byte[1024 * 10]; 196 197 for (int n = 0; n != -1; n = in.read(bytes)) { 198 fout.write(bytes, 0, n); 199 } 200 201 fout.close(); 202 } catch (IOException ioe) { 203 // We might get an IOException trying to overwrite an existing 204 // jline.dll file if there is another process using the DLL. 205 // If this happens, ignore errors. 206 if (!exists) { 207 throw ioe; 208 } 209 } 210 211 // try to clean up the DLL after the JVM exits 212 f.deleteOnExit(); 213 214 // now actually load the DLL 215 System.load(f.getAbsolutePath()); 216 } 217 218 public int readVirtualKey(InputStream in) throws IOException { 219 int c = readCharacter(in); 220 221 // in Windows terminals, arrow keys are represented by 222 // a sequence of 2 characters. E.g., the up arrow 223 // key yields 224, 72 224 if (c == 224) { 225 c = readCharacter(in); 226 227 if (c == 72) { 228 return CTRL_P; // translate UP -> CTRL-P 229 } else if (c == 80) { 230 return CTRL_N; // translate DOWN -> CTRL-N 231 } else if (c == 75) { 232 return CTRL_B; // translate LEFT -> CTRL-B 233 } else if (c == 77) { 234 return CTRL_F; // translate RIGHT -> CTRL-F 235 } 236 } 237 238 return c; 239 } 240 241 public boolean isSupported() { 242 return true; 243 } 244 245 /** 246 * Windows doesn't support ANSI codes by default; disable them. 247 */ 248 public boolean isANSISupported() { 249 return false; 250 } 251 252 public boolean getEcho() { 253 return false; 254 } 255 256 /** 257 * Unsupported; return the default. 258 * 259 * @see Terminal#getTerminalWidth 260 */ 261 public int getTerminalWidth() { 262 return getWindowsTerminalWidth(); 263 } 264 265 /** 266 * Unsupported; return the default. 267 * 268 * @see Terminal#getTerminalHeight 269 */ 270 public int getTerminalHeight() { 271 return getWindowsTerminalHeight(); 272 } 273 274 /** 275 * No-op for exceptions we want to silently consume. 276 */ 277 private void consumeException(final Throwable e) { 278 } 279 280 /** 281 * Whether or not to allow the use of the JNI console interaction. 282 */ 283 public void setDirectConsole(Boolean directConsole) { 284 this.directConsole = directConsole; 285 } 286 287 /** 288 * Whether or not to allow the use of the JNI console interaction. 289 */ 290 public Boolean getDirectConsole() { 291 return this.directConsole; 292 } 293 }