1 /* 2 * Copyright (c) 2002-2012, the original author or authors. 3 * 4 * This software is distributable under the BSD license. See the terms of the 5 * BSD license in the documentation provided with this software. 6 * 7 * http://www.opensource.org/licenses/bsd-license.php 8 */ 9 package jdk.internal.jline.internal; 10 11 import java.io.ByteArrayOutputStream; 12 import java.io.Closeable; 13 import java.io.IOException; 14 import java.io.InputStream; 15 import java.io.OutputStream; 16 import java.text.MessageFormat; 17 import java.util.regex.Matcher; 18 import java.util.regex.Pattern; 19 20 import static jdk.internal.jline.internal.Preconditions.checkNotNull; 21 22 /** 23 * Provides access to terminal line settings via <tt>stty</tt>. 24 * 25 * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> 26 * @author <a href="mailto:dwkemp@gmail.com">Dale Kemp</a> 27 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> 28 * @author <a href="mailto:jbonofre@apache.org">Jean-Baptiste Onofr\u00E9</a> 29 * @since 2.0 30 */ 31 public final class TerminalLineSettings 32 { 33 public static final String JLINE_STTY = "jline.stty"; 34 35 public static final String DEFAULT_STTY = "stty"; 36 37 public static final String JLINE_SH = "jline.sh"; 38 39 public static final String DEFAULT_SH = "sh"; 40 41 private String sttyCommand; 42 43 private String shCommand; 44 45 private String config; 46 private String initialConfig; 47 48 private long configLastFetched; 49 50 public TerminalLineSettings() throws IOException, InterruptedException { 51 sttyCommand = Configuration.getString(JLINE_STTY, DEFAULT_STTY); 52 shCommand = Configuration.getString(JLINE_SH, DEFAULT_SH); 53 initialConfig = get("-g").trim(); 54 config = get("-a"); 55 configLastFetched = System.currentTimeMillis(); 56 57 Log.debug("Config: ", config); 58 59 // sanity check 60 if (config.length() == 0) { 61 throw new IOException(MessageFormat.format("Unrecognized stty code: {0}", config)); 62 } 63 } 64 65 public String getConfig() { 66 return config; 67 } 68 69 public void restore() throws IOException, InterruptedException { 70 set(initialConfig); 71 } 72 73 public String get(final String args) throws IOException, InterruptedException { 74 return stty(args); 75 } 76 77 public void set(final String args) throws IOException, InterruptedException { 78 stty(args); 79 } 80 81 /** 82 * <p> 83 * Get the value of a stty property, including the management of a cache. 84 * </p> 85 * 86 * @param name the stty property. 87 * @return the stty property value. 88 */ 89 public int getProperty(String name) { 90 checkNotNull(name); 91 long currentTime = System.currentTimeMillis(); 92 try { 93 // tty properties are cached so we don't have to worry too much about getting term width/height 94 if (config == null || currentTime - configLastFetched > 1000) { 95 config = get("-a"); 96 } 97 } catch (Exception e) { 98 if (e instanceof InterruptedException) { 99 Thread.currentThread().interrupt(); 100 } 101 Log.debug("Failed to query stty ", name, "\n", e); 102 if (config == null) { 103 return -1; 104 } 105 } 106 107 // always update the last fetched time and try to parse the output 108 if (currentTime - configLastFetched > 1000) { 109 configLastFetched = currentTime; 110 } 111 112 return getProperty(name, config); 113 } 114 115 /** 116 * <p> 117 * Parses a stty output (provided by stty -a) and return the value of a given property. 118 * </p> 119 * 120 * @param name property name. 121 * @param stty string resulting of stty -a execution. 122 * @return value of the given property. 123 */ 124 protected static int getProperty(String name, String stty) { 125 // try the first kind of regex 126 Pattern pattern = Pattern.compile(name + "\\s+=\\s+(.*?)[;\\n\\r]"); 127 Matcher matcher = pattern.matcher(stty); 128 if (!matcher.find()) { 129 // try a second kind of regex 130 pattern = Pattern.compile(name + "\\s+([^;]*)[;\\n\\r]"); 131 matcher = pattern.matcher(stty); 132 if (!matcher.find()) { 133 // try a second try of regex 134 pattern = Pattern.compile("(\\S*)\\s+" + name); 135 matcher = pattern.matcher(stty); 136 if (!matcher.find()) { 137 return -1; 138 } 139 } 140 } 141 return parseControlChar(matcher.group(1)); 142 } 143 144 private static int parseControlChar(String str) { 145 // under 146 if ("<undef>".equals(str)) { 147 return -1; 148 } 149 // octal 150 if (str.charAt(0) == '0') { 151 return Integer.parseInt(str, 8); 152 } 153 // decimal 154 if (str.charAt(0) >= '1' && str.charAt(0) <= '9') { 155 return Integer.parseInt(str, 10); 156 } 157 // control char 158 if (str.charAt(0) == '^') { 159 if (str.charAt(1) == '?') { 160 return 127; 161 } else { 162 return str.charAt(1) - 64; 163 } 164 } else if (str.charAt(0) == 'M' && str.charAt(1) == '-') { 165 if (str.charAt(2) == '^') { 166 if (str.charAt(3) == '?') { 167 return 127 + 128; 168 } else { 169 return str.charAt(3) - 64 + 128; 170 } 171 } else { 172 return str.charAt(2) + 128; 173 } 174 } else { 175 return str.charAt(0); 176 } 177 } 178 179 private String stty(final String args) throws IOException, InterruptedException { 180 checkNotNull(args); 181 return exec(String.format("%s %s < /dev/tty", sttyCommand, args)); 182 } 183 184 private String exec(final String cmd) throws IOException, InterruptedException { 185 checkNotNull(cmd); 186 return exec(shCommand, "-c", cmd); 187 } 188 189 private String exec(final String... cmd) throws IOException, InterruptedException { 190 checkNotNull(cmd); 191 192 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 193 194 Log.trace("Running: ", cmd); 195 196 Process p = Runtime.getRuntime().exec(cmd); 197 198 InputStream in = null; 199 InputStream err = null; 200 OutputStream out = null; 201 try { 202 int c; 203 in = p.getInputStream(); 204 while ((c = in.read()) != -1) { 205 bout.write(c); 206 } 207 err = p.getErrorStream(); 208 while ((c = err.read()) != -1) { 209 bout.write(c); 210 } 211 out = p.getOutputStream(); 212 p.waitFor(); 213 } 214 finally { 215 close(in, out, err); 216 } 217 218 String result = bout.toString(); 219 220 Log.trace("Result: ", result); 221 222 return result; 223 } 224 225 private static void close(final Closeable... closeables) { 226 for (Closeable c : closeables) { 227 try { 228 c.close(); 229 } 230 catch (Exception e) { 231 // Ignore 232 } 233 } 234 } 235 } 236