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