View Javadoc

1   /**
2    *	jline - Java console input library
3    *	Copyright (c) 2002, 2003, 2004, 2005, Marc Prud'hommeaux <mwp1@cornell.edu>
4    *	All rights reserved.
5    *
6    *	Redistribution and use in source and binary forms, with or
7    *	without modification, are permitted provided that the following
8    *	conditions are met:
9    *
10   *	Redistributions of source code must retain the above copyright
11   *	notice, this list of conditions and the following disclaimer.
12   *
13   *	Redistributions in binary form must reproduce the above copyright
14   *	notice, this list of conditions and the following disclaimer
15   *	in the documentation and/or other materials provided with
16   *	the distribution.
17   *
18   *	Neither the name of JLine nor the names of its contributors
19   *	may be used to endorse or promote products derived from this
20   *	software without specific prior written permission.
21   *
22   *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23   *	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24   *	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25   *	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
26   *	EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
27   *	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
28   *	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29   *	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30   *	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31   *	AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32   *	LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33   *	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34   *	OF THE POSSIBILITY OF SUCH DAMAGE.
35   */
36  package jline;
37  
38  import java.io.*;
39  
40  // TODO: handle arrow keys, which might require completely implementing the
41  // console input reading in the .dll. For example, see:
42  // http://cvs.sourceforge.net/viewcvs.py/lifelines/lifelines/
43  // win32/mycurses.c?rev=1.28
44  
45  /**
46   *	<p>
47   *	Terminal implementation for Microsoft Windows. Terminal initialization
48   *	in {@link #initializeTerminal} is accomplished by extracting the
49   *	<em>jline_<i>version</i>.dll</em>, saving it to the system temporary
50   *	directoy (determined by the setting of the <em>java.io.tmpdir</em>
51   *	System property), loading the library, and then calling the Win32 APIs
52   *  <a href="http://msdn.microsoft.com/library/default.asp?
53   *  url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a>
54   *  and
55   *  <a href="http://msdn.microsoft.com/library/default.asp?
56   *  url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a>
57   *  to disable character echoing.
58   *  </p>
59   *
60   *  <p>
61   *  By default, the {@link #readCharacter} method will attempt to test
62   *  to see if the specified {@link InputStream} is {@link System#in}
63   *  or a wrapper around {@link FileDescriptor#in}, and if so, will
64   *  bypass the character reading to directly invoke the
65   *  readc() method in the JNI library. This is so the class can
66   *  read special keys (like arrow keys) which are otherwise
67   *  inaccessible via the {@link System#in} stream. Using JNI
68   *  reading can be bypassed by setting the
69   *  <code>jline.WindowsTerminal.disableDirectConsole</code> system
70   *  property to <code>true</code>.
71   *  </p>
72   *
73   *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
74   */
75  public class WindowsTerminal
76  	extends Terminal
77  {
78  	// constants copied from wincon.h
79  
80  	/**
81  	 *  The ReadFile or ReadConsole function returns only when
82  	 *  a carriage return character is read. If this mode is disable,
83  	 *  the functions return when one or more characters are
84  	 *  available.
85  	 */
86  	private static final int ENABLE_LINE_INPUT			= 2;
87  
88  
89  	/**
90  	 *  Characters read by the ReadFile or ReadConsole function
91  	 *  are written to the active screen buffer as they are read.
92  	 *  This mode can be used only if the ENABLE_LINE_INPUT mode
93  	 *  is also enabled.
94  	 */
95  	private static final int ENABLE_ECHO_INPUT			= 4;
96  
97  
98  	/**
99  	 *  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