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