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