001 /** 002 * jline - Java console input library 003 * Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu> 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * Redistributions of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 013 * Redistributions in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer 015 * in the documentation and/or other materials provided with 016 * the distribution. 017 * 018 * Neither the name of JLine nor the names of its contributors 019 * may be used to endorse or promote products derived from this 020 * software without specific prior written permission. 021 * 022 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 023 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 024 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 025 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 026 * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 027 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 028 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 029 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 030 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 031 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 032 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 033 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 034 * OF THE POSSIBILITY OF SUCH DAMAGE. 035 */ 036 package jline; 037 038 import java.io.*; 039 040 // TODO: handle arrow keys, which might require completely implementing the 041 // console input reading in the .dll. For example, see: 042 // http://cvs.sourceforge.net/viewcvs.py/lifelines/lifelines/ 043 // win32/mycurses.c?rev=1.28 044 045 /** 046 * <p> 047 * Terminal implementation for Microsoft Windows. Terminal initialization 048 * in {@link #initializeTerminal} is accomplished by extracting the 049 * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary 050 * directoy (determined by the setting of the <em>java.io.tmpdir</em> 051 * System property), loading the library, and then calling the Win32 APIs 052 * <a href="http://msdn.microsoft.com/library/default.asp? 053 * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> 054 * and 055 * <a href="http://msdn.microsoft.com/library/default.asp? 056 * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> 057 * to disable character echoing. 058 * </p> 059 * 060 * <p> 061 * By default, the {@link #readCharacter} method will attempt to test 062 * to see if the specified {@link InputStream} is {@link System#in} 063 * or a wrapper around {@link FileDescriptor#in}, and if so, will 064 * bypass the character reading to directly invoke the 065 * readc() method in the JNI library. This is so the class can 066 * read special keys (like arrow keys) which are otherwise 067 * inaccessible via the {@link System#in} stream. Using JNI 068 * reading can be bypassed by setting the 069 * <code>jline.WindowsTerminal.disableDirectConsole</code> system 070 * property to <code>true</code>. 071 * </p> 072 * 073 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 074 */ 075 public class WindowsTerminal 076 extends Terminal 077 { 078 // constants copied from wincon.h 079 080 /** 081 * The ReadFile or ReadConsole function returns only when 082 * a carriage return character is read. If this mode is disable, 083 * the functions return when one or more characters are 084 * available. 085 */ 086 private static final int ENABLE_LINE_INPUT = 2; 087 088 089 /** 090 * Characters read by the ReadFile or ReadConsole function 091 * are written to the active screen buffer as they are read. 092 * This mode can be used only if the ENABLE_LINE_INPUT mode 093 * is also enabled. 094 */ 095 private static final int ENABLE_ECHO_INPUT = 4; 096 097 098 /** 099 * CTRL+C is processed by the system and is not placed 100 * in the input buffer. If the input buffer is being read 101 * by ReadFile or ReadConsole, other control keys are processed 102 * by the system and are not returned in the ReadFile or ReadConsole 103 * buffer. If the ENABLE_LINE_INPUT mode is also enabled, 104 * backspace, carriage return, and linefeed characters are 105 * handled by the system. 106 */ 107 private static final int ENABLE_PROCESSED_INPUT = 1; 108 109 110 /** 111 * User interactions that change the size of the console 112 * screen buffer are reported in the console's input buffee. 113 * Information about these events can be read from the input 114 * buffer by applications using theReadConsoleInput function, 115 * but not by those using ReadFile orReadConsole. 116 */ 117 private static final int ENABLE_WINDOW_INPUT = 8; 118 119 120 /** 121 * If the mouse pointer is within the borders of the console 122 * window and the window has the keyboard focus, mouse events 123 * generated by mouse movement and button presses are placed 124 * in the input buffer. These events are discarded by ReadFile 125 * or ReadConsole, even when this mode is enabled. 126 */ 127 private static final int ENABLE_MOUSE_INPUT = 16; 128 129 130 /** 131 * When enabled, text entered in a console window will 132 * be inserted at the current cursor location and all text 133 * following that location will not be overwritten. When disabled, 134 * all following text will be overwritten. An OR operation 135 * must be performed with this flag and the ENABLE_EXTENDED_FLAGS 136 * flag to enable this functionality. 137 */ 138 private static final int ENABLE_PROCESSED_OUTPUT = 1; 139 140 141 /** 142 * This flag enables the user to use the mouse to select 143 * and edit text. To enable this option, use the OR to combine 144 * this flag with ENABLE_EXTENDED_FLAGS. 145 */ 146 private static final int ENABLE_WRAP_AT_EOL_OUTPUT = 2; 147 148 149 private Boolean directConsole; 150 151 152 public WindowsTerminal () 153 { 154 String dir = System.getProperty ("jline.WindowsTerminal.directConsole"); 155 if ("true".equals (dir)) 156 directConsole = Boolean.TRUE; 157 else if ("false".equals (dir)) 158 directConsole = Boolean.FALSE; 159 } 160 161 162 private native int getConsoleMode (); 163 164 private native void setConsoleMode (final int mode); 165 166 private native int readByte (); 167 168 private native int getWindowsTerminalWidth (); 169 170 private native int getWindowsTerminalHeight (); 171 172 173 public int readCharacter (final InputStream in) 174 throws IOException 175 { 176 // if we can detect that we are directly wrapping the system 177 // input, then bypass the input stream and read directly (which 178 // allows us to access otherwise unreadable strokes, such as 179 // the arrow keys) 180 if (directConsole == Boolean.FALSE) 181 return super.readCharacter (in); 182 else if (directConsole == Boolean.TRUE || 183 ((in == System.in || (in instanceof FileInputStream && 184 ((FileInputStream)in).getFD () == FileDescriptor.in)))) 185 return readByte (); 186 else 187 return super.readCharacter (in); 188 } 189 190 191 public void initializeTerminal () 192 throws Exception 193 { 194 loadLibrary ("jline"); 195 196 final int originalMode = getConsoleMode (); 197 198 setConsoleMode (originalMode & ~ENABLE_ECHO_INPUT); 199 200 // set the console to raw mode 201 int newMode = originalMode 202 & ~(ENABLE_LINE_INPUT 203 | ENABLE_ECHO_INPUT 204 | ENABLE_PROCESSED_INPUT 205 | ENABLE_WINDOW_INPUT); 206 setConsoleMode (newMode); 207 208 // at exit, restore the original tty configuration (for JDK 1.3+) 209 try 210 { 211 Runtime.getRuntime ().addShutdownHook (new Thread () 212 { 213 public void start () 214 { 215 // restore the old console mode 216 setConsoleMode (originalMode); 217 } 218 }); 219 } 220 catch (AbstractMethodError ame) 221 { 222 // JDK 1.3+ only method. Bummer. 223 consumeException (ame); 224 } 225 } 226 227 228 private void loadLibrary (final String name) 229 throws IOException 230 { 231 // store the DLL in the temporary directory for the System 232 String version = getClass ().getPackage ().getImplementationVersion (); 233 if (version == null) 234 version = ""; 235 version = version.replace ('.', '_'); 236 237 File f = new File (System.getProperty ("java.io.tmpdir"), 238 name + "_" + version + ".dll"); 239 boolean exists = f.isFile (); // check if it already exists 240 241 // extract the embedded jline.dll file from the jar and save 242 // it to the current directory 243 InputStream in = new BufferedInputStream (getClass () 244 .getResourceAsStream (name + ".dll")); 245 246 try 247 { 248 OutputStream fout = new BufferedOutputStream ( 249 new FileOutputStream (f)); 250 byte[] bytes = new byte [1024 * 10]; 251 for (int n = 0; n != -1; n = in.read (bytes)) 252 fout.write (bytes, 0, n); 253 254 fout.close (); 255 } 256 catch (IOException ioe) 257 { 258 // We might get an IOException trying to overwrite an existing 259 // jline.dll file if there is another process using the DLL. 260 // If this happens, ignore errors. 261 if (!exists) 262 throw ioe; 263 } 264 265 // try to clean up the DLL after the JVM exits 266 f.deleteOnExit (); 267 268 // now actually load the DLL 269 System.load (f.getAbsolutePath ()); 270 } 271 272 273 public int readVirtualKey (InputStream in) 274 throws IOException 275 { 276 int c = readCharacter (in); 277 278 // in Windows terminals, arrow keys are represented by 279 // a sequence of 2 characters. E.g., the up arrow 280 // key yields 224, 72 281 if (c == 224) 282 { 283 c = readCharacter (in); 284 if (c == 72) 285 return CTRL_P; // translate UP -> CTRL-P 286 else if (c == 80) 287 return CTRL_N; // translate DOWN -> CTRL-N 288 else if (c == 75) 289 return CTRL_B; // translate LEFT -> CTRL-B 290 else if (c == 77) 291 return CTRL_F; // translate RIGHT -> CTRL-F 292 } 293 294 return c; 295 } 296 297 298 public boolean isSupported () 299 { 300 return true; 301 } 302 303 304 /** 305 * Windows doesn't support ANSI codes by default; disable them. 306 */ 307 public boolean isANSISupported () 308 { 309 return false; 310 } 311 312 313 public boolean getEcho () 314 { 315 return false; 316 } 317 318 319 /** 320 * Unsupported; return the default. 321 * 322 * @see Terminal#getTerminalWidth 323 */ 324 public int getTerminalWidth () 325 { 326 return getWindowsTerminalWidth (); 327 } 328 329 330 /** 331 * Unsupported; return the default. 332 * 333 * @see Terminal#getTerminalHeight 334 */ 335 public int getTerminalHeight () 336 { 337 return getWindowsTerminalHeight (); 338 } 339 340 341 /** 342 * No-op for exceptions we want to silently consume. 343 */ 344 private void consumeException (final Throwable e) 345 { 346 } 347 348 349 /** 350 * Whether or not to allow the use of the JNI console interaction. 351 */ 352 public void setDirectConsole (Boolean directConsole) 353 { 354 this.directConsole = directConsole; 355 } 356 357 358 /** 359 * Whether or not to allow the use of the JNI console interaction. 360 */ 361 public Boolean getDirectConsole () 362 { 363 return this.directConsole; 364 } 365 366 367 } 368