    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    import java.util.*;
    040
    041
    042    /**
    043     *  <p>
    044     *  Terminal that is used for unix platforms. Terminal initialization
    045     *  is handled by issuing the <em>stty</em> command against the
    046     *  <em>/dev/tty</em> file to disable character echoing and enable
    047     *  character input. All known unix systems (including
    048     *  Linux and Macintosh OS X) support the <em>stty</em>), so this
    049     *  implementation should work for an reasonable POSIX system.
    050     *      </p>
    051     *
    052     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
    053     */
    054    public class UnixTerminal
    055            extends Terminal
    056    {
    057            public static final short ARROW_START           = 27;
    058            public static final short ARROW_PREFIX          = 91;
    059            public static final short ARROW_LEFT            = 68;
    060            public static final short ARROW_RIGHT           = 67;
    061            public static final short ARROW_UP              = 65;
    062            public static final short ARROW_DOWN            = 66;
    063
    064            private Map terminfo;
    065            private int width = -1;
    066            private int height = -1;
    067
    068
    069            /**
    070             *  Remove line-buffered input by invoking "stty -icanon min 1"
    071             *  against the current terminal.
    072             */
    073            public void initializeTerminal ()
    074                    throws IOException, InterruptedException
    075            {
    076                    // save the initial tty configuration
    077                    final String ttyConfig = stty ("-g");
    078
    079                    // sanity check
    080                    if (ttyConfig.length () == 0
    081                            || (ttyConfig.indexOf ("=") == -1
    082                            && ttyConfig.indexOf (":") == -1))
    083                    {
    084                            throw new IOException ("Unrecognized stty code: " + ttyConfig);
    085                    }
    086
    087
    088                    // set the console to be character-buffered instead of line-buffered
    089                    stty ("-icanon min 1");
    090
    091                    // disable character echoing
    092                    stty ("-echo");
    093
    094                    // at exit, restore the original tty configuration (for JDK 1.3+)
    095                    try
    096                    {
    097                            Runtime.getRuntime ().addShutdownHook (new Thread ()
    098                            {
    099                                    public void start ()
    100                                    {
    101                                            try
    102                                            {
    103                                                    stty (ttyConfig);
    104                                            }
    105                                            catch (Exception e)
    106                                            {
    107                                                    consumeException (e);
    108                                            }
    109                                    }
    110                            });
    111                    }
    112                    catch (AbstractMethodError ame)
    113                    {
    114                            // JDK 1.3+ only method. Bummer.
    115                            consumeException (ame);
    116                    }
    117            }
    118
    119
    120            public int readVirtualKey (InputStream in)
    121                    throws IOException
    122            {
    123                    int c = readCharacter (in);
    124
    125                    // in Unix terminals, arrow keys are represented by
    126                    // a sequence of 3 characters. E.g., the up arrow
    127                    // key yields 27, 91, 68
    128                    if (c == ARROW_START)
    129                    {
    130                            c = readCharacter (in);
    131                            if (c == ARROW_PREFIX)
    132                            {
    133                                    c = readCharacter (in);
    134                                    if (c == ARROW_UP)
    135                                            return CTRL_P;
    136                                    else if (c == ARROW_DOWN)
    137                                            return CTRL_N;
    138                                    else if (c == ARROW_LEFT)
    139                                            return CTRL_B;
    140                                    else if (c == ARROW_RIGHT)
    141                                            return CTRL_F;
    142                            }
    143                    }
    144
    145
    146                    return c;
    147            }
    148
    149
    150            /**
    151             *  No-op for exceptions we want to silently consume.
    152             */
    153            private void consumeException (Throwable e)
    154            {
    155            }
    156
    157
    158            public boolean isSupported ()
    159            {
    160                    return true;
    161            }
    162
    163
    164            public boolean getEcho ()
    165            {
    166                    return false;
    167            }
    168
    169
    170            /**
    171             *      Returns the value of "stty size" width param.
    172             *
    173             *      <strong>Note</strong>: this method caches the value from the
    174             *      first time it is called in order to increase speed, which means
    175             *      that changing to size of the terminal will not be reflected
    176             *      in the console.
    177             */
    178            public int getTerminalWidth ()
    179            {
    180                    if (width != -1)
    181                            return width;
    182
    183                    int val = 80;
    184                    try
    185                    {
    186                            String size = stty ("size");
    187                            if (size.length () != 0 && size.indexOf (" ") != -1)
    188                            {
    189                                    val = Integer.parseInt (
    190                                            size.substring (size.indexOf (" ") + 1));
    191                            }
    192                    }
    193                    catch (Exception e)
    194                    {
    195                            consumeException (e);
    196                    }
    197
    198                    return width = val;
    199            }
    200
    201
    202            /**
    203             *      Returns the value of "stty size" height param.
    204             *
    205             *      <strong>Note</strong>: this method caches the value from the
    206             *      first time it is called in order to increase speed, which means
    207             *      that changing to size of the terminal will not be reflected
    208             *      in the console.
    209             */
    210            public int getTerminalHeight ()
    211            {
    212                    if (height != -1)
    213                            return height;
    214
    215                    int val = 24;
    216
    217                    try
    218                    {
    219                            String size = stty ("size");
    220                            if (size.length () != 0 && size.indexOf (" ") != -1)
    221                            {
    222                                    val = Integer.parseInt (
    223                                            size.substring (0, size.indexOf (" ")));
    224                            }
    225                    }
    226                    catch (Exception e)
    227                    {
    228                    }
    229
    230                    return height = val;
    231            }
    232
    233
    234            /**
    235             *  Execute the stty command with the specified arguments
    236             *  against the current active terminal.
    237             */
    238            private static String stty (final String args)
    239                    throws IOException, InterruptedException
    240            {
    241                    return exec ("stty " + args + " < /dev/tty").trim ();
    242            }
    243
    244
    245            /**
    246             *  Execute the specified command and return the output
    247             *  (both stdout and stderr).
    248             */
    249            private static String exec (final String cmd)
    250                    throws IOException, InterruptedException
    251            {
    252                    return exec (new String [] { "sh", "-c", cmd });
    253            }
    254
    255
    256            /**
    257             *  Execute the specified command and return the output
    258             *  (both stdout and stderr).
    259             */
    260            private static String exec (final String [] cmd)
    261                    throws IOException, InterruptedException
    262            {
    263                    ByteArrayOutputStream bout = new ByteArrayOutputStream ();
    264
    265                    Process p = Runtime.getRuntime ().exec (cmd);
    266                    int c;
    267                    InputStream in;
    268
    269                    in = p.getInputStream ();
    270                    while ((c = in.read ()) != -1)
    271                            bout.write (c);
    272
    273                    in = p.getErrorStream ();
    274                    while ((c = in.read ()) != -1)
    275                            bout.write (c);
    276
    277                    p.waitFor ();
    278
    279                    String result = new String (bout.toByteArray ());
    280                    return result;
    281            }
    282    }
    283