/* * Copyright (c) 2002-2016, the original author or authors. * * This software is distributable under the BSD license. See the terms of the * BSD license in the documentation provided with this software. * * http://www.opensource.org/licenses/bsd-license.php */ package jline; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import jline.internal.Configuration; import jline.internal.InfoCmp; import jline.internal.Log; import jline.internal.TerminalLineSettings; import static jline.internal.Preconditions.checkNotNull; /** * Terminal that is used for unix platforms. Terminal initialization * is handled by issuing the <em>stty</em> command against the * <em>/dev/tty</em> file to disable character echoing and enable * character input. All known unix systems (including * Linux and Macintosh OS X) support the <em>stty</em>), so this * implementation should work for an reasonable POSIX system. * * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a> * @author <a href="mailto:dwkemp@gmail.com">Dale Kemp</a> * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> * @author <a href="mailto:jbonofre@apache.org">Jean-Baptiste Onofr��</a> * @since 2.0 */ public class UnixTerminal extends TerminalSupport implements Terminal2 { private final TerminalLineSettings settings; private final String type; private String intr; private String lnext; private Set<String> bools = new HashSet<String>(); private Map<String, Integer> ints = new HashMap<String, Integer>(); private Map<String, String> strings = new HashMap<String, String>(); public UnixTerminal() throws Exception { this(TerminalLineSettings.DEFAULT_TTY, null); } public UnixTerminal(String ttyDevice) throws Exception { this(ttyDevice, null); } public UnixTerminal(String ttyDevice, String type) throws Exception { super(true); checkNotNull(ttyDevice); this.settings = TerminalLineSettings.getSettings(ttyDevice); if (type == null) { type = System.getenv("TERM"); } this.type = type; parseInfoCmp(); } public TerminalLineSettings getSettings() { return settings; } /** * Remove line-buffered input by invoking "stty -icanon min 1" * against the current terminal. */ @Override public void init() throws Exception { super.init(); setAnsiSupported(true); // Set the console to be character-buffered instead of line-buffered. // Make sure we're distinguishing carriage return from newline. // Allow ctrl-s keypress to be used (as forward search) // // Please note that FreeBSD does not seem to support -icrnl and thus // has to be handled separately. Otherwise the console will be "stuck" // and will neither accept input nor print anything to stdout. if (Configuration.getOsName().contains(TerminalFactory.FREEBSD)) { settings.set("-icanon min 1 -inlcr -ixon"); } else { settings.set("-icanon min 1 -icrnl -inlcr -ixon"); } settings.undef("dsusp"); setEchoEnabled(false); parseInfoCmp(); } /** * Restore the original terminal configuration, which can be used when * shutting down the console reader. The ConsoleReader cannot be * used after calling this method. */ @Override public void restore() throws Exception { settings.restore(); super.restore(); } /** * Returns the value of <tt>stty columns</tt> param. */ @Override public int getWidth() { int w = settings.getProperty("columns"); return w < 1 ? DEFAULT_WIDTH : w; } /** * Returns the value of <tt>stty rows>/tt> param. */ @Override public int getHeight() { int h = settings.getProperty("rows"); return h < 1 ? DEFAULT_HEIGHT : h; } @Override public boolean hasWeirdWrap() { return getBooleanCapability("auto_right_margin") && getBooleanCapability("eat_newline_glitch"); } @Override public synchronized void setEchoEnabled(final boolean enabled) { try { if (enabled) { settings.set("echo"); } else { settings.set("-echo"); } super.setEchoEnabled(enabled); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } Log.error("Failed to ", enabled ? "enable" : "disable", " echo", e); } } public void disableInterruptCharacter() { try { intr = getSettings().getPropertyAsString("intr"); if ("<undef>".equals(intr)) { intr = null; } settings.undef("intr"); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } Log.error("Failed to disable interrupt character", e); } } public void enableInterruptCharacter() { try { if (intr != null) { settings.set("intr", intr); } } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } Log.error("Failed to enable interrupt character", e); } } public void disableLitteralNextCharacter() { try { lnext = getSettings().getPropertyAsString("lnext"); if ("<undef>".equals(lnext)) { lnext = null; } settings.undef("lnext"); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } Log.error("Failed to disable litteral next character", e); } } public void enableLitteralNextCharacter() { try { if (lnext != null) { settings.set("lnext", lnext); } } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } Log.error("Failed to enable litteral next character", e); } } public boolean getBooleanCapability(String capability) { return bools.contains(capability); } public Integer getNumericCapability(String capability) { return ints.get(capability); } public String getStringCapability(String capability) { return strings.get(capability); } private void parseInfoCmp() { String capabilities = null; if (type != null) { try { capabilities = InfoCmp.getInfoCmp(type); } catch (Exception e) { } } if (capabilities == null) { capabilities = InfoCmp.getAnsiCaps(); } InfoCmp.parseInfoCmp(capabilities, bools, ints, strings); } }