| 1 | /* |
| 2 | * This file is part of lanterna (http://code.google.com/p/lanterna/). |
| 3 | * |
| 4 | * lanterna is free software: you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU Lesser General Public License as published by |
| 6 | * the Free Software Foundation, either version 3 of the License, or |
| 7 | * (at your option) any later version. |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | * GNU Lesser General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Lesser General Public License |
| 15 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | * |
| 17 | * Copyright (C) 2010-2015 Martin |
| 18 | */ |
| 19 | package com.googlecode.lanterna.terminal.ansi; |
| 20 | |
| 21 | import com.googlecode.lanterna.TerminalSize; |
| 22 | |
| 23 | import java.io.*; |
| 24 | import java.nio.charset.Charset; |
| 25 | import java.util.ArrayList; |
| 26 | import java.util.Arrays; |
| 27 | import java.util.List; |
| 28 | import java.util.regex.Matcher; |
| 29 | import java.util.regex.Pattern; |
| 30 | |
| 31 | /** |
| 32 | * This class extends UnixLikeTerminal and implements the Cygwin-specific implementations. This means, running a Java |
| 33 | * application using Lanterna inside the Cygwin Terminal application. The standard Windows command prompt (cmd.exe) is |
| 34 | * not supported by this class.<p> |
| 35 | * <p> |
| 36 | * <b>NOTE:</b> This class is experimental and does not fully work! Some of the operations, like disabling echo and |
| 37 | * changing cbreak seems to be impossible to do without resorting to native code. Running "stty raw" before starting the |
| 38 | * JVM will improve compatibility. |
| 39 | * |
| 40 | * @author Martin |
| 41 | * @author Andreas |
| 42 | */ |
| 43 | public class CygwinTerminal extends UnixLikeTerminal { |
| 44 | |
| 45 | private static final Pattern STTY_SIZE_PATTERN = Pattern.compile(".*rows ([0-9]+);.*columns ([0-9]+);.*"); |
| 46 | private static final String STTY_LOCATION = findProgram("stty.exe"); |
| 47 | |
| 48 | /** |
| 49 | * Creates a new CygwinTerminal based off input and output streams and a character set to use |
| 50 | * @param terminalInput Input stream to read input from |
| 51 | * @param terminalOutput Output stream to write output to |
| 52 | * @param terminalCharset Character set to use when writing to the output stream |
| 53 | * @throws IOException If there was an I/O error when trying to initialize the class and setup the terminal |
| 54 | */ |
| 55 | public CygwinTerminal( |
| 56 | InputStream terminalInput, |
| 57 | OutputStream terminalOutput, |
| 58 | Charset terminalCharset) throws IOException { |
| 59 | super(terminalInput, terminalOutput, terminalCharset, |
| 60 | CtrlCBehaviour.TRAP, null); |
| 61 | |
| 62 | //Make sure to set an initial size |
| 63 | onResized(80, 24); |
| 64 | |
| 65 | saveSTTY(); |
| 66 | setCBreak(true); |
| 67 | setEcho(false); |
| 68 | sttyMinimum1CharacterForRead(); |
| 69 | setupShutdownHook(); |
| 70 | } |
| 71 | |
| 72 | @Override |
| 73 | public TerminalSize getTerminalSize() { |
| 74 | try { |
| 75 | String stty = exec(findSTTY(), "-F", getPseudoTerminalDevice(), "-a"); |
| 76 | Matcher matcher = STTY_SIZE_PATTERN.matcher(stty); |
| 77 | if(matcher.matches()) { |
| 78 | return new TerminalSize(Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(1))); |
| 79 | } |
| 80 | else { |
| 81 | return new TerminalSize(80, 24); |
| 82 | } |
| 83 | } |
| 84 | catch(Throwable e) { |
| 85 | return new TerminalSize(80, 24); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | @Override |
| 90 | protected void sttyKeyEcho(final boolean enable) throws IOException { |
| 91 | runSTTYCommand(enable ? "echo" : "-echo"); |
| 92 | } |
| 93 | |
| 94 | @Override |
| 95 | protected void sttyMinimum1CharacterForRead() throws IOException { |
| 96 | runSTTYCommand("min", "1"); |
| 97 | } |
| 98 | |
| 99 | @Override |
| 100 | protected void sttyICanon(final boolean enable) throws IOException { |
| 101 | runSTTYCommand(enable ? "icanon" : "cbreak"); |
| 102 | } |
| 103 | |
| 104 | @Override |
| 105 | protected String sttySave() throws IOException { |
| 106 | return runSTTYCommand("-g").trim(); |
| 107 | } |
| 108 | |
| 109 | @Override |
| 110 | protected void sttyRestore(String tok) throws IOException { |
| 111 | runSTTYCommand(tok); |
| 112 | } |
| 113 | |
| 114 | protected String findSTTY() { |
| 115 | return STTY_LOCATION; |
| 116 | } |
| 117 | |
| 118 | private String runSTTYCommand(String... parameters) throws IOException { |
| 119 | List<String> commandLine = new ArrayList<String>(Arrays.asList( |
| 120 | findSTTY(), |
| 121 | "-F", |
| 122 | getPseudoTerminalDevice())); |
| 123 | commandLine.addAll(Arrays.asList(parameters)); |
| 124 | return exec(commandLine.toArray(new String[commandLine.size()])); |
| 125 | } |
| 126 | |
| 127 | private String getPseudoTerminalDevice() { |
| 128 | //This will only work if you only have one terminal window open, otherwise we'll need to figure out somehow |
| 129 | //which pty to use, which could be very tricky... |
| 130 | return "/dev/pty0"; |
| 131 | } |
| 132 | |
| 133 | private static String findProgram(String programName) { |
| 134 | String[] paths = System.getProperty("java.library.path").split(";"); |
| 135 | for(String path : paths) { |
| 136 | File shBin = new File(path, programName); |
| 137 | if(shBin.exists()) { |
| 138 | return shBin.getAbsolutePath(); |
| 139 | } |
| 140 | } |
| 141 | return programName; |
| 142 | } |
| 143 | |
| 144 | } |