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 }