001 /** 002 * jline - Java console input library 003 * Copyright (c) 2002,2003 Marc Prud'hommeaux marc@apocalypse.org 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library; if not, write to the Free Software 017 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018 */ 019 package jline; 020 021 import java.io.*; 022 import java.util.*; 023 024 025 /** 026 * Representation of the input terminal for a platform. Handles 027 * any initialization that the platform may need to perform 028 * in order to allow the {@link ConsoleReader} to correctly handle 029 * input. 030 * 031 * @author <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a> 032 */ 033 public abstract class Terminal 034 { 035 private static Terminal term; 036 037 038 /** 039 * Configure and return the {@link Terminal} instance for the 040 * current platform. This will initialize any system settings 041 * that are required for the console to be able to handle 042 * input correctly, such as setting tabtop, buffered input, and 043 * character echo. 044 * 045 * @see #initializeTerminal 046 */ 047 public static synchronized Terminal setupTerminal () 048 { 049 if (term != null) 050 return term; 051 052 final Terminal t; 053 054 String os = System.getProperty ("os.name").toLowerCase (); 055 if (os.indexOf ("windows") != -1) 056 t = new WindowsTerminal (); 057 else 058 t = new UnixTerminal (); 059 060 try 061 { 062 t.initializeTerminal (); 063 } 064 catch (Exception e) 065 { 066 e.printStackTrace (); 067 } 068 069 return term = t; 070 } 071 072 073 /** 074 * Initialize any system settings 075 * that are required for the console to be able to handle 076 * input correctly, such as setting tabtop, buffered input, and 077 * character echo. 078 */ 079 public abstract void initializeTerminal () 080 throws Exception; 081 082 083 /** 084 * Returns the current width of the terminal (in characters) 085 */ 086 public abstract int getTerminalWidth (); 087 088 089 /** 090 * Returns the current height of the terminal (in lines) 091 */ 092 public abstract int getTerminalHeight (); 093 094 095 096 /** 097 * <p>Terminal that is used for Unix platforms.</p> 098 * 099 * <p> 100 * <strong>WARNING:</strong> this class executes the "stty" 101 * commmand using {@link Runtime#exec} in order to set and query 102 * various terminal parameters. It will fail in a strict security, 103 * and standard disclaimers about java programs that fork separate 104 * commands apply. It also requires that "stty" is in the user's 105 * PATH variable (which it almost always is). 106 * </p> 107 * 108 * @author <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a> 109 */ 110 public static class UnixTerminal 111 extends Terminal 112 { 113 private Map terminfo; 114 private int width = -1; 115 private int height = -1; 116 117 118 /** 119 * Remove line-buffered input by invoking "stty -icanon min 1" 120 * against the current terminal. 121 */ 122 public void initializeTerminal () 123 throws IOException, InterruptedException 124 { 125 // save the initial tty configuration 126 final String ttyConfig = stty ("-g"); 127 128 // sanity check 129 if (ttyConfig.length () == 0 130 || ttyConfig.indexOf ("=") == -1 131 || ttyConfig.indexOf (":") == -1) 132 { 133 throw new IOException ("Unrecognized stty code: " + ttyConfig); 134 } 135 136 137 // set the console to be character-buffered instead of line-buffered 138 stty ("-icanon min 1"); 139 140 // at exit, restore the original tty configuration (for JDK 1.3+) 141 try 142 { 143 Runtime.getRuntime ().addShutdownHook (new Thread () 144 { 145 public void start () 146 { 147 try 148 { 149 stty (ttyConfig); 150 } 151 catch (Exception e) 152 { 153 e.printStackTrace (); 154 } 155 } 156 }); 157 } 158 catch (AbstractMethodError ame) 159 { 160 // JDK 1.3+ only method. Bummer. 161 } 162 } 163 164 165 /** 166 * Returns the value of "stty size" width param. 167 * 168 * <strong>Note</strong>: this method caches the value from the 169 * first time it is called in order to increase speed, which means 170 * that changing to size of the terminal will not be reflected 171 * in the console. 172 */ 173 public int getTerminalWidth () 174 { 175 if (width != -1) 176 return width; 177 178 int val = 80; 179 try 180 { 181 String size = stty ("size"); 182 if (size.length () != 0 && size.indexOf (" ") != -1) 183 { 184 val = Integer.parseInt ( 185 size.substring (size.indexOf (" ") + 1)); 186 } 187 } 188 catch (Exception e) 189 { 190 } 191 return width = val; 192 } 193 194 195 /** 196 * Returns the value of "stty size" height param. 197 * 198 * <strong>Note</strong>: this method caches the value from the 199 * first time it is called in order to increase speed, which means 200 * that changing to size of the terminal will not be reflected 201 * in the console. 202 */ 203 public int getTerminalHeight () 204 { 205 if (height != -1) 206 return height; 207 208 int val = 24; 209 210 try 211 { 212 String size = stty ("size"); 213 if (size.length () != 0 && size.indexOf (" ") != -1) 214 { 215 val = Integer.parseInt ( 216 size.substring (0, size.indexOf (" "))); 217 } 218 } 219 catch (Exception e) 220 { 221 } 222 223 return height = val; 224 } 225 226 227 /** 228 * Execute the stty command with the specified arguments 229 * against the current active terminal. 230 */ 231 private static String stty (String args) 232 throws IOException, InterruptedException 233 { 234 return exec ("stty " + args + " < /dev/tty").trim (); 235 } 236 237 238 /** 239 * Execute the specified command and return the output 240 * (both stdout and stderr). 241 */ 242 private static String exec (String cmd) 243 throws IOException, InterruptedException 244 { 245 return exec (new String [] { "sh", "-c", cmd }); 246 } 247 248 249 /** 250 * Execute the specified command and return the output 251 * (both stdout and stderr). 252 */ 253 private static String exec (String [] cmd) 254 throws IOException, InterruptedException 255 { 256 ByteArrayOutputStream bout = new ByteArrayOutputStream (); 257 258 Process p = Runtime.getRuntime ().exec (cmd); 259 int c; 260 InputStream in; 261 262 in = p.getInputStream (); 263 while ((c = in.read ()) != -1) 264 bout.write (c); 265 266 in = p.getErrorStream (); 267 while ((c = in.read ()) != -1) 268 bout.write (c); 269 270 p.waitFor (); 271 272 String result = new String (bout.toByteArray ()); 273 return result; 274 } 275 } 276 277 278 /** 279 * Terminal that is used for Windows platforms. 280 * 281 * @author <a href="mailto:marc@apocalypse.org">Marc Prud'hommeaux</a> 282 */ 283 public static class WindowsTerminal 284 extends Terminal 285 { 286 public void initializeTerminal () 287 { 288 // nothing we need to do (or can do) for windows. 289 } 290 291 292 /** 293 * Always returng 80, since we can't access this info on Windows. 294 */ 295 public int getTerminalWidth () 296 { 297 return 80; 298 } 299 300 301 /** 302 * Always returng 24, since we can't access this info on Windows. 303 */ 304 public int getTerminalHeight () 305 { 306 return 80; 307 } 308 } 309 }