001 /*
002 * Copyright (c) 2002-2006, Marc Prud'hommeaux. All rights reserved.
003 *
004 * This software is distributable under the BSD license. See the terms of the
005 * BSD license in the documentation provided with this software.
006 */
007 package jline;
008
009 import java.io.*;
010 import java.util.*;
011
012 /**
013 * <p>
014 * Terminal that is used for unix platforms. Terminal initialization
015 * is handled by issuing the <em>stty</em> command against the
016 * <em>/dev/tty</em> file to disable character echoing and enable
017 * character input. All known unix systems (including
018 * Linux and Macintosh OS X) support the <em>stty</em>), so this
019 * implementation should work for an reasonable POSIX system.
020 * </p>
021 *
022 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
023 * @author Updates <a href="mailto:dwkemp@gmail.com">Dale Kemp</a> 2005-12-03
024 */
025 public class UnixTerminal extends Terminal {
026 public static final short ARROW_START = 27;
027 public static final short ARROW_PREFIX = 91;
028 public static final short ARROW_LEFT = 68;
029 public static final short ARROW_RIGHT = 67;
030 public static final short ARROW_UP = 65;
031 public static final short ARROW_DOWN = 66;
032 public static final short HOME_CODE = 72;
033 public static final short END_CODE = 70;
034 private Map terminfo;
035 private static String sttyCommand =
036 System.getProperty("jline.sttyCommand", "stty");
037
038 /**
039 * Remove line-buffered input by invoking "stty -icanon min 1"
040 * against the current terminal.
041 */
042 public void initializeTerminal() throws IOException, InterruptedException {
043 // save the initial tty configuration
044 final String ttyConfig = stty("-g");
045
046 // sanity check
047 if ((ttyConfig.length() == 0)
048 || ((ttyConfig.indexOf("=") == -1)
049 && (ttyConfig.indexOf(":") == -1))) {
050 throw new IOException("Unrecognized stty code: " + ttyConfig);
051 }
052
053 // set the console to be character-buffered instead of line-buffered
054 stty("-icanon min 1");
055
056 // disable character echoing
057 stty("-echo");
058
059 // at exit, restore the original tty configuration (for JDK 1.3+)
060 try {
061 Runtime.getRuntime().addShutdownHook(new Thread() {
062 public void start() {
063 try {
064 stty(ttyConfig);
065 } catch (Exception e) {
066 consumeException(e);
067 }
068 }
069 });
070 } catch (AbstractMethodError ame) {
071 // JDK 1.3+ only method. Bummer.
072 consumeException(ame);
073 }
074 }
075
076 public int readVirtualKey(InputStream in) throws IOException {
077 int c = readCharacter(in);
078
079 // in Unix terminals, arrow keys are represented by
080 // a sequence of 3 characters. E.g., the up arrow
081 // key yields 27, 91, 68
082 if (c == ARROW_START) {
083 c = readCharacter(in);
084
085 if (c == ARROW_PREFIX) {
086 c = readCharacter(in);
087
088 if (c == ARROW_UP) {
089 return CTRL_P;
090 } else if (c == ARROW_DOWN) {
091 return CTRL_N;
092 } else if (c == ARROW_LEFT) {
093 return CTRL_B;
094 } else if (c == ARROW_RIGHT) {
095 return CTRL_F;
096 } else if (c == HOME_CODE) {
097 return CTRL_A;
098 } else if (c == END_CODE) {
099 return CTRL_E;
100 }
101 }
102 }
103
104 return c;
105 }
106
107 /**
108 * No-op for exceptions we want to silently consume.
109 */
110 private void consumeException(Throwable e) {
111 }
112
113 public boolean isSupported() {
114 return true;
115 }
116
117 public boolean getEcho() {
118 return false;
119 }
120
121 /**
122 * Returns the value of "stty size" width param.
123 *
124 * <strong>Note</strong>: this method caches the value from the
125 * first time it is called in order to increase speed, which means
126 * that changing to size of the terminal will not be reflected
127 * in the console.
128 */
129 public int getTerminalWidth() {
130 int val = -1;
131
132 try {
133 val = getTerminalProperty("columns");
134 } catch (Exception e) {
135 }
136
137 if (val == -1) {
138 val = 80;
139 }
140
141 return val;
142 }
143
144 /**
145 * Returns the value of "stty size" height param.
146 *
147 * <strong>Note</strong>: this method caches the value from the
148 * first time it is called in order to increase speed, which means
149 * that changing to size of the terminal will not be reflected
150 * in the console.
151 */
152 public int getTerminalHeight() {
153 int val = -1;
154
155 try {
156 val = getTerminalProperty("rows");
157 } catch (Exception e) {
158 }
159
160 if (val == -1) {
161 val = 24;
162 }
163
164 return val;
165 }
166
167 private static int getTerminalProperty(String prop)
168 throws IOException, InterruptedException {
169 // need to be able handle both output formats:
170 // speed 9600 baud; 24 rows; 140 columns;
171 // and:
172 // speed 38400 baud; rows = 49; columns = 111; ypixels = 0; xpixels = 0;
173 String props = stty("-a");
174
175 for (StringTokenizer tok = new StringTokenizer(props, ";\n");
176 tok.hasMoreTokens();) {
177 String str = tok.nextToken().trim();
178
179 if (str.startsWith(prop)) {
180 int index = str.lastIndexOf(" ");
181
182 return Integer.parseInt(str.substring(index).trim());
183 } else if (str.endsWith(prop)) {
184 int index = str.indexOf(" ");
185
186 return Integer.parseInt(str.substring(0, index).trim());
187 }
188 }
189
190 return -1;
191 }
192
193 /**
194 * Execute the stty command with the specified arguments
195 * against the current active terminal.
196 */
197 private static String stty(final String args)
198 throws IOException, InterruptedException {
199 return exec("stty " + args + " < /dev/tty").trim();
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 return exec(new String[] {
209 "sh",
210 "-c",
211 cmd
212 });
213 }
214
215 /**
216 * Execute the specified command and return the output
217 * (both stdout and stderr).
218 */
219 private static String exec(final String[] cmd)
220 throws IOException, InterruptedException {
221 ByteArrayOutputStream bout = new ByteArrayOutputStream();
222
223 Process p = Runtime.getRuntime().exec(cmd);
224 int c;
225 InputStream in;
226
227 in = p.getInputStream();
228
229 while ((c = in.read()) != -1) {
230 bout.write(c);
231 }
232
233 in = p.getErrorStream();
234
235 while ((c = in.read()) != -1) {
236 bout.write(c);
237 }
238
239 p.waitFor();
240
241 String result = new String(bout.toByteArray());
242
243 return result;
244 }
245
246 /**
247 * The command to use to set the terminal options. Defaults
248 * to "stty", or the value of the system property "jline.sttyCommand".
249 */
250 public static void setSttyCommand(String cmd) {
251 sttyCommand = cmd;
252 }
253
254 /**
255 * The command to use to set the terminal options. Defaults
256 * to "stty", or the value of the system property "jline.sttyCommand".
257 */
258 public static String getSttyCommand() {
259 return sttyCommand;
260 }
261
262 public static void main(String[] args) {
263 System.out.println("width: " + new UnixTerminal().getTerminalWidth());
264 System.out.println("height: " + new UnixTerminal().getTerminalHeight());
265 }
266 }