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 }