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