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