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