001    /**
002     *      jline - Java console input library
003     *      Copyright (c) 2002,2003 Marc Prud'hommeaux mwp1@cornell.edu
004     *      
005     *      This library is free software; you can redistribute it and/or
006     *      modify it under the terms of the GNU Lesser General Public
007     *      License as published by the Free Software Foundation; either
008     *      version 2.1 of the License, or (at your option) any later version.
009     *      
010     *      This library is distributed in the hope that it will be useful,
011     *      but WITHOUT ANY WARRANTY; without even the implied warranty of
012     *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     *      Lesser General Public License for more details.
014     *      
015     *      You should have received a copy of the GNU Lesser General Public
016     *      License along with this library; if not, write to the Free Software
017     *      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018     */
019    package jline;
020    
021    import java.io.*;
022    import java.util.*;
023    
024    // TODO: handle arrow keys, which might require completely implementing the
025    // console input reading in the .dll. For example, see:
026    // http://cvs.sourceforge.net/viewcvs.py/lifelines/lifelines/
027    // win32/mycurses.c?rev=1.28
028    
029    /**
030     *      <p>
031     *      Terminal implementation for Microsoft Windows. Terminal initialization
032     *      in {@link #initializeTerminal} is accomplished by extracting the
033     *      <em>jline_<i>version</i>.dll</em>, saving it to the system temporary
034     *      directoy (determined by the setting of the <em>java.io.tmpdir</em>
035     *      System property), loading the library, and then calling the Win32 APIs
036     *  <a href="http://msdn.microsoft.com/library/default.asp?
037     *  url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a>
038     *  and
039     *  <a href="http://msdn.microsoft.com/library/default.asp?
040     *  url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a>
041     *  to disable character echoing.
042     *  </p>
043     *
044     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
045     */
046    public class WindowsTerminal
047            extends Terminal
048    {
049            // constants copied from wincon.h
050    
051            /**
052             *  The ReadFile or ReadConsole function returns only when
053             *  a carriage return character is read. If this mode is disable,
054             *  the functions return when one or more characters are
055             *  available.
056             */
057            private static final int ENABLE_LINE_INPUT                      = 2;
058    
059    
060            /**
061             *  Characters read by the ReadFile or ReadConsole function
062             *  are written to the active screen buffer as they are read.
063             *  This mode can be used only if the ENABLE_LINE_INPUT mode
064             *  is also enabled.
065             */
066            private static final int ENABLE_ECHO_INPUT                      = 4;
067    
068    
069            /**
070             *  CTRL+C is processed by the system and is not placed
071             *  in the input buffer. If the input buffer is being read
072             *  by ReadFile or ReadConsole, other control keys are processed
073             *  by the system and are not returned in the ReadFile or ReadConsole
074             *  buffer. If the ENABLE_LINE_INPUT mode is also enabled,
075             *  backspace, carriage return, and linefeed characters are
076             *  handled by the system.
077             */
078            private static final int ENABLE_PROCESSED_INPUT         = 1;
079    
080    
081            /**
082             *  User interactions that change the size of the console
083             *  screen buffer are reported in the console's input buffee.
084             *  Information about these events can be read from the input
085             *  buffer by applications using theReadConsoleInput function,
086             *  but not by those using ReadFile orReadConsole.
087             */
088            private static final int ENABLE_WINDOW_INPUT            = 8;
089    
090    
091            /**
092             *  If the mouse pointer is within the borders of the console
093             *  window and the window has the keyboard focus, mouse events
094             *  generated by mouse movement and button presses are placed
095             *  in the input buffer. These events are discarded by ReadFile
096             *  or ReadConsole, even when this mode is enabled.
097             */
098            private static final int ENABLE_MOUSE_INPUT                     = 16;
099    
100    
101            /**
102             *  When enabled, text entered in a console window will
103             *  be inserted at the current cursor location and all text
104             *  following that location will not be overwritten. When disabled,
105             *  all following text will be overwritten. An OR operation
106             *  must be performed with this flag and the ENABLE_EXTENDED_FLAGS
107             *  flag to enable this functionality.
108             */
109            private static final int ENABLE_PROCESSED_OUTPUT        = 1;
110    
111    
112            /**
113             *  This flag enables the user to use the mouse to select
114             *  and edit text. To enable this option, use the OR to combine
115             *  this flag with ENABLE_EXTENDED_FLAGS.
116             */
117            private static final int ENABLE_WRAP_AT_EOL_OUTPUT      = 2;
118    
119    
120    
121            private native int getConsoleMode ();
122    
123            private native void setConsoleMode (final int mode);
124    
125    
126            public void initializeTerminal ()
127                    throws Exception
128            {
129                    loadLibrary ("jline");
130    
131                    final int originalMode = getConsoleMode ();
132    
133                    setConsoleMode (originalMode & ~ENABLE_ECHO_INPUT);
134    
135                    // set the console to raw mode
136                    int newMode = originalMode
137                            & ~(ENABLE_LINE_INPUT
138                                    | ENABLE_ECHO_INPUT
139                                    | ENABLE_PROCESSED_INPUT
140                                    | ENABLE_WINDOW_INPUT);
141                    setConsoleMode (newMode);
142    
143                    // at exit, restore the original tty configuration (for JDK 1.3+)
144                    try
145                    {
146                            Runtime.getRuntime ().addShutdownHook (new Thread ()
147                            {
148                                    public void start ()
149                                    {
150                                            // restore the old console mode
151                                            setConsoleMode (originalMode);
152                                    }
153                            });
154                    }
155                    catch (AbstractMethodError ame)
156                    {
157                            // JDK 1.3+ only method. Bummer.
158                            consumeException (ame);
159                    }
160            }
161    
162    
163            private void loadLibrary (final String name)
164                    throws IOException
165            {
166                    // store the DLL in the temporary directory for the System
167                    String version = getClass ().getPackage ().getImplementationVersion ();
168                    if (version == null)
169                            version = "";
170                    version = version.replace ('.', '_');
171    
172                    File f = new File (System.getProperty ("java.io.tmpdir"),
173                            name + "_" + version + ".dll");
174                    boolean exists = f.isFile (); // check if it already exists
175    
176                    // extract the embedded jline.dll file from the jar and save
177                    // it to the current directory
178                    InputStream in = new BufferedInputStream (getClass ()
179                            .getResourceAsStream (name + ".dll"));
180    
181                    try
182                    {
183                            OutputStream fout = new BufferedOutputStream (
184                                    new FileOutputStream (f));
185                            byte[] bytes = new byte [1024 * 10];
186                            for (int n = 0; n != -1; n = in.read (bytes))
187                                    fout.write (bytes, 0, n);
188    
189                            fout.close ();
190                    }
191                    catch (IOException ioe)
192                    {
193                            // We might get an IOException trying to overwrite an existing
194                            // jline.dll file if there is another process using the DLL.
195                            // If this happens, ignore errors.
196                            if (!exists)
197                                    throw ioe;
198                    }
199    
200                    // try to clean up the DLL after the JVM exits
201                    f.deleteOnExit ();
202    
203                    // now actually load the DLL
204                    System.load (f.getAbsolutePath ());
205            }
206    
207    
208            public boolean isSupported ()
209            {
210                    return true;
211            }
212    
213    
214            public boolean getEcho ()
215            {
216                    return false;
217            }
218    
219    
220            /**
221             *  Unsupported; return the default.
222             *
223             *  @see Terminal#getTerminalWidth
224             */
225            public int getTerminalWidth ()
226            {
227                    return 80;
228            }
229    
230    
231            /**
232             *  Unsupported; return the default.
233             *
234             *  @see Terminal#getTerminalHeight
235             */
236            public int getTerminalHeight ()
237            {
238                    return 24;
239            }
240    
241    
242            /** 
243             *  No-op for exceptions we want to silently consume.
244             */
245            private void consumeException (final Throwable e)
246            {
247            }
248    }
249