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 }