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