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    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     *  @author  Updates <a href="mailto:dwkemp@gmail.com">Dale Kemp</a> 2005-12-03
054     */
055    public class UnixTerminal
056            extends Terminal
057    {
058            public static final short ARROW_START           = 27;
059            public static final short ARROW_PREFIX          = 91;
060            public static final short ARROW_LEFT            = 68;
061            public static final short ARROW_RIGHT           = 67;
062            public static final short ARROW_UP              = 65;
063            public static final short ARROW_DOWN            = 66;
064            public static final short HOME_CODE                     = 72;
065            public static final short END_CODE                              = 70;
066    
067            private Map terminfo;
068    
069    
070            /**
071             *  Remove line-buffered input by invoking "stty -icanon min 1"
072             *  against the current terminal.
073             */
074            public void initializeTerminal ()
075                    throws IOException, InterruptedException
076            {
077                    // save the initial tty configuration
078                    final String ttyConfig = stty ("-g");
079    
080                    // sanity check
081                    if (ttyConfig.length () == 0
082                            || (ttyConfig.indexOf ("=") == -1
083                            && ttyConfig.indexOf (":") == -1))
084                    {
085                            throw new IOException ("Unrecognized stty code: " + ttyConfig);
086                    }
087    
088    
089                    // set the console to be character-buffered instead of line-buffered
090                    stty ("-icanon min 1");
091    
092                    // disable character echoing
093                    stty ("-echo");
094    
095                    // at exit, restore the original tty configuration (for JDK 1.3+)
096                    try
097                    {
098                            Runtime.getRuntime ().addShutdownHook (new Thread ()
099                            {
100                                    public void start ()
101                                    {
102                                            try
103                                            {
104                                                    stty (ttyConfig);
105                                            }
106                                            catch (Exception e)
107                                            {
108                                                    consumeException (e);
109                                            }
110                                    }
111                            });
112                    }
113                    catch (AbstractMethodError ame)
114                    {
115                            // JDK 1.3+ only method. Bummer.
116                            consumeException (ame);
117                    }
118            }
119    
120    
121            public int readVirtualKey (InputStream in)
122                    throws IOException
123            {
124                    int c = readCharacter (in);
125    
126                    // in Unix terminals, arrow keys are represented by
127                    // a sequence of 3 characters. E.g., the up arrow
128                    // key yields 27, 91, 68
129                    if (c == ARROW_START)
130                    {
131                            c = readCharacter (in);
132                            if (c == ARROW_PREFIX)
133                            {
134                                    c = readCharacter (in);
135                                    if (c == ARROW_UP)
136                                            return CTRL_P;
137                                    else if (c == ARROW_DOWN)
138                                            return CTRL_N;
139                                    else if (c == ARROW_LEFT)
140                                            return CTRL_B;
141                                    else if (c == ARROW_RIGHT)
142                                            return CTRL_F;
143                                    else if (c == HOME_CODE)
144                                            return CTRL_A;
145                                    else if (c == END_CODE)
146                                            return CTRL_E;
147                            }
148                    }
149    
150    
151                    return c;
152            }
153    
154    
155            /** 
156             *  No-op for exceptions we want to silently consume.
157             */
158            private void consumeException (Throwable e)
159            {
160            }
161    
162    
163            public boolean isSupported ()
164            {
165                    return true;
166            }
167    
168    
169            public boolean getEcho ()
170            {
171                    return false;
172            }
173    
174    
175            /**
176             *      Returns the value of "stty size" width param.
177             *
178             *      <strong>Note</strong>: this method caches the value from the
179             *      first time it is called in order to increase speed, which means
180             *      that changing to size of the terminal will not be reflected
181             *      in the console.
182             */
183            public int getTerminalWidth ()
184            {
185                    int val = -1;
186    
187                    try
188                    {
189                            val = getTerminalProperty ("columns");
190                    }
191                    catch (Exception e)
192                    {
193                    }
194    
195                    if (val == -1)
196                            val = 80;
197    
198                    return 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                    int val = -1;
213    
214                    try
215                    {
216                            val = getTerminalProperty ("rows");
217                    }
218                    catch (Exception e)
219                    {
220                    }
221    
222                    if (val == -1)
223                            val = 24;
224    
225                    return val;
226            }
227    
228    
229            private static int getTerminalProperty (String prop)
230                    throws IOException, InterruptedException
231            {
232                    // need to be able handle both output formats:
233                    // speed 9600 baud; 24 rows; 140 columns;
234                    // and:
235                    // speed 38400 baud; rows = 49; columns = 111; ypixels = 0; xpixels = 0;
236                    String props = stty ("-a");
237    
238                    for (StringTokenizer tok = new StringTokenizer (props, ";\n");
239                            tok.hasMoreTokens (); )
240                    {
241                            String str = tok.nextToken ().trim ();
242                            if (str.startsWith (prop))
243                            {
244                                    int index = str.lastIndexOf (" ");
245                                    return Integer.parseInt (str.substring (index).trim ());
246                            }
247                            else if (str.endsWith (prop))
248                            {
249                                    int index = str.indexOf (" ");
250                                    return Integer.parseInt (str.substring (0, index).trim ());
251                            }
252                    }
253    
254                    return -1;
255            }
256    
257    
258            /**
259             *  Execute the stty command with the specified arguments
260             *  against the current active terminal.
261             */
262            private static String stty (final String args)
263                    throws IOException, InterruptedException
264            {
265                    return exec ("stty " + args + " < /dev/tty").trim ();
266            }
267    
268    
269            /**
270             *  Execute the specified command and return the output
271             *  (both stdout and stderr).
272             */
273            private static String exec (final String cmd)
274                    throws IOException, InterruptedException
275            {
276                    return exec (new String [] { "sh", "-c", cmd });
277            }
278    
279    
280            /**
281             *  Execute the specified command and return the output
282             *  (both stdout and stderr).
283             */
284            private static String exec (final String [] cmd)
285                    throws IOException, InterruptedException
286            {
287                    ByteArrayOutputStream bout = new ByteArrayOutputStream ();
288    
289                    Process p = Runtime.getRuntime ().exec (cmd);
290                    int c;
291                    InputStream in;
292                            
293                    in = p.getInputStream ();
294                    while ((c = in.read ()) != -1)
295                            bout.write (c);
296    
297                    in = p.getErrorStream ();
298                    while ((c = in.read ()) != -1)
299                            bout.write (c);
300    
301                    p.waitFor ();
302    
303                    String result = new String (bout.toByteArray ());
304                    return result;
305            }
306    
307    
308            public static void main (String[] args)
309            {
310                    System.out.println ("width: " + new UnixTerminal ().getTerminalWidth ());
311                    System.out.println ("height: " + new UnixTerminal ().getTerminalHeight ());
312            }
313    }
314