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