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    
025    /**
026     *  <p>
027     *  Terminal that is used for unix platforms. Terminal initialization
028     *  is handled by issuing the <em>stty</em> command against the
029     *  <em>/dev/tty</em> file to disable character echoing and enable
030     *  character input. All known unix systems (including
031     *  Linux and Macintosh OS X) support the <em>stty</em>), so this
032     *  implementation should work for an reasonable POSIX system.
033     *      </p>
034     *
035     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
036     */
037    public class UnixTerminal
038            extends Terminal
039    {
040            private Map terminfo;
041            private int width = -1;
042            private int height = -1;
043    
044    
045            /**
046             *  Remove line-buffered input by invoking "stty -icanon min 1"
047             *  against the current terminal.
048             */
049            public void initializeTerminal ()
050                    throws IOException, InterruptedException
051            {
052                    // save the initial tty configuration
053                    final String ttyConfig = stty ("-g");
054    
055                    // sanity check
056                    if (ttyConfig.length () == 0
057                            || (ttyConfig.indexOf ("=") == -1
058                            && ttyConfig.indexOf (":") == -1))
059                    {
060                            throw new IOException ("Unrecognized stty code: " + ttyConfig);
061                    }
062    
063    
064                    // set the console to be character-buffered instead of line-buffered
065                    stty ("-icanon min 1");
066    
067                    // disable character echoing
068                    stty ("-echo");
069    
070                    // at exit, restore the original tty configuration (for JDK 1.3+)
071                    try
072                    {
073                            Runtime.getRuntime ().addShutdownHook (new Thread ()
074                            {
075                                    public void start ()
076                                    {
077                                            try
078                                            {
079                                                    stty (ttyConfig);
080                                            }
081                                            catch (Exception e)
082                                            {
083                                                    consumeException (e);
084                                            }
085                                    }
086                            });
087                    }
088                    catch (AbstractMethodError ame)
089                    {
090                            // JDK 1.3+ only method. Bummer.
091                            consumeException (ame);
092                    }
093            }
094    
095    
096            /** 
097             *  No-op for exceptions we want to silently consume.
098             */
099            private void consumeException (Throwable e)
100            {
101            }
102    
103    
104            public boolean isSupported ()
105            {
106                    return true;
107            }
108    
109    
110            public boolean getEcho ()
111            {
112                    return false;
113            }
114    
115    
116            /**
117             *      Returns the value of "stty size" width param.
118             *
119             *      <strong>Note</strong>: this method caches the value from the
120             *      first time it is called in order to increase speed, which means
121             *      that changing to size of the terminal will not be reflected
122             *      in the console.
123             */
124            public int getTerminalWidth ()
125            {
126                    if (width != -1)
127                            return width;
128    
129                    int val = 80;
130                    try
131                    {
132                            String size = stty ("size");
133                            if (size.length () != 0 && size.indexOf (" ") != -1)
134                            {
135                                    val = Integer.parseInt (
136                                            size.substring (size.indexOf (" ") + 1));
137                            }
138                    }
139                    catch (Exception e)
140                    {
141                            consumeException (e);
142                    }
143    
144                    return width = val;
145            }
146    
147    
148            /**
149             *      Returns the value of "stty size" height param.
150             *
151             *      <strong>Note</strong>: this method caches the value from the
152             *      first time it is called in order to increase speed, which means
153             *      that changing to size of the terminal will not be reflected
154             *      in the console.
155             */
156            public int getTerminalHeight ()
157            {
158                    if (height != -1)
159                            return height;
160    
161                    int val = 24;
162    
163                    try
164                    {
165                            String size = stty ("size");
166                            if (size.length () != 0 && size.indexOf (" ") != -1)
167                            {
168                                    val = Integer.parseInt (
169                                            size.substring (0, size.indexOf (" ")));
170                            }
171                    }
172                    catch (Exception e)
173                    {
174                    }
175    
176                    return height = val;
177            }
178    
179    
180            /**
181             *  Execute the stty command with the specified arguments
182             *  against the current active terminal.
183             */
184            private static String stty (final String args)
185                    throws IOException, InterruptedException
186            {
187                    return exec ("stty " + args + " < /dev/tty").trim ();
188            }
189    
190    
191            /**
192             *  Execute the specified command and return the output
193             *  (both stdout and stderr).
194             */
195            private static String exec (final String cmd)
196                    throws IOException, InterruptedException
197            {
198                    return exec (new String [] { "sh", "-c", cmd });
199            }
200    
201    
202            /**
203             *  Execute the specified command and return the output
204             *  (both stdout and stderr).
205             */
206            private static String exec (final String [] cmd)
207                    throws IOException, InterruptedException
208            {
209                    ByteArrayOutputStream bout = new ByteArrayOutputStream ();
210    
211                    Process p = Runtime.getRuntime ().exec (cmd);
212                    int c;
213                    InputStream in;
214                            
215                    in = p.getInputStream ();
216                    while ((c = in.read ()) != -1)
217                            bout.write (c);
218    
219                    in = p.getErrorStream ();
220                    while ((c = in.read ()) != -1)
221                            bout.write (c);
222    
223                    p.waitFor ();
224    
225                    String result = new String (bout.toByteArray ());
226                    return result;
227            }
228    }
229