    001    /**
    002     *      jline - Java console input library
    003     *      Copyright (c) 2002, 2003, 2004, 2005, 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