001    /**
002     * jline - Java console input library
003     * Copyright (c) 2002,2003 Marc Prud'hommeaux marc@apocalypse.org
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     *  Representation of the input terminal for a platform. Handles
027     *      any initialization that the platform may need to perform
028     *      in order to allow the {@link ConsoleReader} to correctly handle
029     *      input.
030     *  
031     *  @author  <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a>
032     */
033    public abstract class Terminal
034    {
035            private static Terminal term;
036    
037    
038            /** 
039             *  Configure and return the {@link Terminal} instance for the
040             *  current platform. This will initialize any system settings
041             *  that are required for the console to be able to handle
042             *  input correctly, such as setting tabtop, buffered input, and
043             *  character echo.
044             *
045             *  @see #initializeTerminal
046             */
047            public static synchronized Terminal setupTerminal ()
048            {
049                    if (term != null)
050                            return term;
051    
052                    final Terminal t;
053    
054                    String os = System.getProperty ("os.name").toLowerCase ();
055                    if (os.indexOf ("windows") != -1)
056                            t = new WindowsTerminal ();
057                    else
058                            t = new UnixTerminal ();
059    
060                    try
061                    {
062                            t.initializeTerminal ();
063                    }
064                    catch (Exception e)
065                    {
066                            e.printStackTrace ();
067                    }
068    
069                    return term = t;
070            }
071    
072    
073            /** 
074             *  Initialize any system settings
075             *  that are required for the console to be able to handle
076             *  input correctly, such as setting tabtop, buffered input, and
077             *  character echo.
078             */
079            public abstract void initializeTerminal ()
080                    throws Exception;
081    
082    
083            /** 
084             *  Returns the current width of the terminal (in characters)
085             */
086            public abstract int getTerminalWidth ();
087    
088    
089            /** 
090             *  Returns the current height of the terminal (in lines)
091             */
092            public abstract int getTerminalHeight ();
093    
094    
095    
096            /** 
097             *  <p>Terminal that is used for Unix platforms.</p>
098             *
099             *      <p>
100             *  <strong>WARNING:</strong> this class executes the "stty"
101             *  commmand using {@link Runtime#exec} in order to set and query
102             *  various terminal parameters. It will fail in a strict security,
103             *  and standard disclaimers about java programs that fork separate
104             *  commands apply. It also requires that "stty" is in the user's
105             *  PATH variable (which it almost always is).
106             *      </p>
107             *  
108             *  @author  <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a>
109             */
110            public static class UnixTerminal
111                    extends Terminal
112            {
113                    private Map terminfo;
114                    private int width = -1;
115                    private int height = -1;
116    
117    
118                    /** 
119                     *  Remove line-buffered input by invoking "stty -icanon min 1"
120                     *  against the current terminal.
121                     */
122                    public void initializeTerminal ()
123                            throws IOException, InterruptedException
124                    {
125                            // save the initial tty configuration
126                            final String ttyConfig = stty ("-g");
127    
128                            // sanity check
129                            if (ttyConfig.length () == 0
130                                    || ttyConfig.indexOf ("=") == -1
131                                    || ttyConfig.indexOf (":") == -1)
132                            {
133                                    throw new IOException ("Unrecognized stty code: " + ttyConfig);
134                            }
135    
136    
137                            // set the console to be character-buffered instead of line-buffered
138                            stty ("-icanon min 1");
139    
140                            // at exit, restore the original tty configuration (for JDK 1.3+)
141                            try
142                            {
143                                    Runtime.getRuntime ().addShutdownHook (new Thread ()
144                                    {
145                                            public void start ()
146                                            {
147                                                    try
148                                                    {
149                                                            stty (ttyConfig);
150                                                    }
151                                                    catch (Exception e)
152                                                    {
153                                                            e.printStackTrace ();
154                                                    }
155                                            }
156                                    });
157                            }
158                            catch (AbstractMethodError ame)
159                            {
160                                    // JDK 1.3+ only method. Bummer.
161                            }
162                    }
163    
164    
165                    /** 
166                     *      Returns the value of "stty size" width param.
167                     *
168                     *      <strong>Note</strong>: this method caches the value from the
169                     *      first time it is called in order to increase speed, which means
170                     *      that changing to size of the terminal will not be reflected
171                     *      in the console.
172                     */
173                    public int getTerminalWidth ()
174                    {
175                            if (width != -1)
176                                    return width;
177    
178                            int val = 80;
179                            try
180                            {
181                                    String size = stty ("size");
182                                    if (size.length () != 0 && size.indexOf (" ") != -1)
183                                    {
184                                            val = Integer.parseInt (
185                                                    size.substring (size.indexOf (" ") + 1));
186                                    }
187                            }
188                            catch (Exception e)
189                            {
190                            }
191                            return width = val;
192                    }
193            
194            
195                    /** 
196                     *      Returns the value of "stty size" height param.
197                     *
198                     *      <strong>Note</strong>: this method caches the value from the
199                     *      first time it is called in order to increase speed, which means
200                     *      that changing to size of the terminal will not be reflected
201                     *      in the console.
202                     */
203                    public int getTerminalHeight ()
204                    {
205                            if (height != -1)
206                                    return height;
207    
208                            int val = 24;
209    
210                            try
211                            {
212                                    String size = stty ("size");
213                                    if (size.length () != 0 && size.indexOf (" ") != -1)
214                                    {
215                                            val = Integer.parseInt (
216                                                    size.substring (0, size.indexOf (" ")));
217                                    }
218                            }
219                            catch (Exception e)
220                            {
221                            }
222    
223                            return height = val;
224                    }
225    
226    
227                    /** 
228                     *  Execute the stty command with the specified arguments
229                     *  against the current active terminal.
230                     */
231                    private static String stty (String args)
232                            throws IOException, InterruptedException
233                    {
234                            return exec ("stty " + args + " < /dev/tty").trim ();
235                    }
236    
237    
238                    /** 
239                     *  Execute the specified command and return the output
240                     *  (both stdout and stderr).
241                     */
242                    private static String exec (String cmd)
243                            throws IOException, InterruptedException
244                    {
245                            return exec (new String [] { "sh", "-c", cmd });
246                    }
247    
248    
249                    /** 
250                     *  Execute the specified command and return the output
251                     *  (both stdout and stderr).
252                     */
253                    private static String exec (String [] cmd)
254                            throws IOException, InterruptedException
255                    {
256                            ByteArrayOutputStream bout = new ByteArrayOutputStream ();
257            
258                            Process p = Runtime.getRuntime ().exec (cmd);
259                            int c;
260                            InputStream in;
261                                    
262                            in = p.getInputStream ();
263                            while ((c = in.read ()) != -1)
264                                    bout.write (c);
265            
266                            in = p.getErrorStream ();
267                            while ((c = in.read ()) != -1)
268                                    bout.write (c);
269            
270                            p.waitFor ();
271            
272                            String result = new String (bout.toByteArray ());
273                            return result;
274                    }
275            }
276    
277    
278            /** 
279             *  Terminal that is used for Windows platforms.
280             *  
281             *  @author  <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a>
282             */
283            public static class WindowsTerminal
284                    extends Terminal
285            {
286                    public void initializeTerminal ()
287                    {
288                            // nothing we need to do (or can do) for windows.
289                    }
290    
291    
292                    /** 
293                     *      Always returng 80, since we can't access this info on Windows.
294                     */
295                    public int getTerminalWidth ()
296                    {
297                            return 80;
298                    }
299            
300            
301                    /** 
302                     *      Always returng 24, since we can't access this info on Windows.
303                     */
304                    public int getTerminalHeight ()
305                    {
306                            return 80;
307                    }
308            }
309    }