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 InputStream in = new BufferedInputStream(getClass()
271 .getResourceAsStream(name + ".dll"));
272
273 try {
274 OutputStream fout = new BufferedOutputStream(
275 new FileOutputStream(f));
276 byte[] bytes = new byte[1024 * 10];
277
278 for (int n = 0; n != -1; n = in.read(bytes)) {
279 fout.write(bytes, 0, n);
280 }
281
282 fout.close();
283 } catch (IOException ioe) {
284 // We might get an IOException trying to overwrite an existing
285 // jline.dll file if there is another process using the DLL.
286 // If this happens, ignore errors.
287 if (!exists) {
288 throw ioe;
289 }
290 }
291
292 // try to clean up the DLL after the JVM exits
293 f.deleteOnExit();
294
295 // now actually load the DLL
296 System.load(f.getAbsolutePath());
297 }
298
299 public int readVirtualKey(InputStream in) throws IOException {
300 int indicator = readCharacter(in);
301
302 // in Windows terminals, arrow keys are represented by
303 // a sequence of 2 characters. E.g., the up arrow
304 // key yields 224, 72
305 if (indicator == SPECIAL_KEY_INDICATOR
306 || indicator == NUMPAD_KEY_INDICATOR) {
307 int key = readCharacter(in);
308
309 switch (key) {
310 case UP_ARROW_KEY:
311 return CTRL_P; // translate UP -> CTRL-P
312 case LEFT_ARROW_KEY:
313 return CTRL_B; // translate LEFT -> CTRL-B
314 case RIGHT_ARROW_KEY:
315 return CTRL_F; // translate RIGHT -> CTRL-F
316 case DOWN_ARROW_KEY:
317 return CTRL_N; // translate DOWN -> CTRL-N
318 case DELETE_KEY:
319 return CTRL_QM; // translate DELETE -> CTRL-?
320 case HOME_KEY:
321 return CTRL_A;
322 case END_KEY:
323 return CTRL_E;
324 case PAGE_UP_KEY:
325 return CTRL_K;
326 case PAGE_DOWN_KEY:
327 return CTRL_L;
328 case ESCAPE_KEY:
329 return CTRL_OB; // translate ESCAPE -> CTRL-[
330 case INSERT_KEY:
331 return CTRL_C;
332 default:
333 return 0;
334 }
335 } else {
336 return indicator;
337 }
338 }
339
340 public boolean isSupported() {
341 return true;
342 }
343
344 /**
345 * Windows doesn't support ANSI codes by default; disable them.
346 */
347 public boolean isANSISupported() {
348 return false;
349 }
350
351 public boolean getEcho() {
352 return false;
353 }
354
355 /**
356 * Unsupported; return the default.
357 *
358 * @see Terminal#getTerminalWidth
359 */
360 public int getTerminalWidth() {
361 return getWindowsTerminalWidth();
362 }
363
364 /**
365 * Unsupported; return the default.
366 *
367 * @see Terminal#getTerminalHeight
368 */
369 public int getTerminalHeight() {
370 return getWindowsTerminalHeight();
371 }
372
373 /**
374 * No-op for exceptions we want to silently consume.
375 */
376 private void consumeException(final Throwable e) {
377 }
378
379 /**
380 * Whether or not to allow the use of the JNI console interaction.
381 */
382 public void setDirectConsole(Boolean directConsole) {
383 this.directConsole = directConsole;
384 }
385
386 /**
387 * Whether or not to allow the use of the JNI console interaction.
388 */
389 public Boolean getDirectConsole() {
390 return this.directConsole;
391 }
392
393 public synchronized boolean isEchoEnabled() {
394 return echoEnabled;
395 }
396
397 public synchronized void enableEcho() {
398 // Must set these four modes at the same time to make it work fine.
399 setConsoleMode(getConsoleMode() | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT
400 | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT);
401 echoEnabled = true;
402 }
403
404 public synchronized void disableEcho() {
405 // Must set these four modes at the same time to make it work fine.
406 setConsoleMode(getConsoleMode()
407 & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT
408 | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT));
409 echoEnabled = true;
410 }
411
412 public InputStream getDefaultBindings() {
413 return getClass().getResourceAsStream("windowsbindings.properties");
414 }
415 }