+++ /dev/null
-/*
- * This file is part of lanterna (http://code.google.com/p/lanterna/).
- *
- * lanterna is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright (C) 2010-2015 Martin
- */
-package com.googlecode.lanterna.terminal.ansi;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-
-import com.googlecode.lanterna.Symbols;
-import com.googlecode.lanterna.input.InputDecoder;
-import com.googlecode.lanterna.input.KeyDecodingProfile;
-import com.googlecode.lanterna.input.KeyStroke;
-import com.googlecode.lanterna.input.ScreenInfoAction;
-import com.googlecode.lanterna.input.ScreenInfoCharacterPattern;
-import com.googlecode.lanterna.terminal.AbstractTerminal;
-import com.googlecode.lanterna.TerminalPosition;
-import com.googlecode.lanterna.TerminalSize;
-import java.io.ByteArrayOutputStream;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * An abstract terminal implementing functionality for terminals using OutputStream/InputStream. You can extend from
- * this class if your terminal implementation is using standard input and standard output but not ANSI escape codes (in
- * which case you should extend ANSITerminal). This class also contains some automatic UTF-8 to VT100 character
- * conversion when the terminal is not set to read UTF-8.
- *
- * @author Martin
- */
-public abstract class StreamBasedTerminal extends AbstractTerminal {
-
- private static final Charset UTF8_REFERENCE = Charset.forName("UTF-8");
-
- private final InputStream terminalInput;
- private final OutputStream terminalOutput;
- private final Charset terminalCharset;
-
- private final InputDecoder inputDecoder;
- private final Queue<KeyStroke> keyQueue;
- private final Lock readLock;
-
- @SuppressWarnings("WeakerAccess")
- public StreamBasedTerminal(InputStream terminalInput, OutputStream terminalOutput, Charset terminalCharset) {
- this.terminalInput = terminalInput;
- this.terminalOutput = terminalOutput;
- if(terminalCharset == null) {
- this.terminalCharset = Charset.defaultCharset();
- }
- else {
- this.terminalCharset = terminalCharset;
- }
- this.inputDecoder = new InputDecoder(new InputStreamReader(this.terminalInput, this.terminalCharset));
- this.keyQueue = new LinkedList<KeyStroke>();
- this.readLock = new ReentrantLock();
- //noinspection ConstantConditions
- }
-
- /**
- * {@inheritDoc}
- *
- * The {@code StreamBasedTerminal} class will attempt to translate some unicode characters to VT100 if the encoding
- * attached to this {@code Terminal} isn't UTF-8.
- */
- @Override
- public void putCharacter(char c) throws IOException {
- writeToTerminal(translateCharacter(c));
- }
-
- /**
- * This method will write a list of bytes directly to the output stream of the terminal.
- * @param bytes Bytes to write to the terminal (synchronized)
- * @throws java.io.IOException If there was an underlying I/O error
- */
- @SuppressWarnings("WeakerAccess")
- protected void writeToTerminal(byte... bytes) throws IOException {
- synchronized(terminalOutput) {
- terminalOutput.write(bytes);
- }
- }
-
- @Override
- public byte[] enquireTerminal(int timeout, TimeUnit timeoutTimeUnit) throws IOException {
- synchronized(terminalOutput) {
- terminalOutput.write(5); //ENQ
- flush();
- }
-
- //Wait for input
- long startTime = System.currentTimeMillis();
- while(terminalInput.available() == 0) {
- if(System.currentTimeMillis() - startTime > timeoutTimeUnit.toMillis(timeout)) {
- return new byte[0];
- }
- try {
- Thread.sleep(1);
- }
- catch(InterruptedException e) {
- return new byte[0];
- }
- }
-
- //We have at least one character, read as far as we can and return
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- while(terminalInput.available() > 0) {
- buffer.write(terminalInput.read());
- }
- return buffer.toByteArray();
- }
-
- /**
- * Adds a KeyDecodingProfile to be used when converting raw user input characters to {@code Key} objects.
- *
- * @see KeyDecodingProfile
- * @param profile Decoding profile to add
- * @deprecated Use {@code getInputDecoder().addProfile(profile)} instead
- */
- @Deprecated
- @SuppressWarnings("WeakerAccess")
- public void addKeyDecodingProfile(KeyDecodingProfile profile) {
- inputDecoder.addProfile(profile);
- }
-
- /**
- * Returns the {@code InputDecoder} attached to this {@code StreamBasedTerminal}. Can be used to add additional
- * character patterns to recognize and tune the way input is turned in {@code KeyStroke}:s.
- * @return {@code InputDecoder} attached to this {@code StreamBasedTerminal}
- */
- public InputDecoder getInputDecoder() {
- return inputDecoder;
- }
-
- @SuppressWarnings("ConstantConditions")
- TerminalSize waitForTerminalSizeReport() throws IOException {
- long startTime = System.currentTimeMillis();
- readLock.lock();
- try {
- while(true) {
- KeyStroke key = inputDecoder.getNextCharacter(false);
- if(key == null) {
- if(System.currentTimeMillis() - startTime > 1000) { //Wait 1 second for the terminal size report to come, is this reasonable?
- throw new IOException(
- "Timeout while waiting for terminal size report! Your terminal may have refused to go into cbreak mode.");
- }
- try {
- Thread.sleep(1);
- }
- catch(InterruptedException ignored) {}
- continue;
- }
-
- // check both: real ScreenInfoActions and F3 keystrokes with modifiers:
- ScreenInfoAction report = ScreenInfoCharacterPattern.tryToAdopt(key);
- if (report == null) {
- keyQueue.add(key);
- }
- else {
- TerminalPosition reportedTerminalPosition = report.getPosition();
- onResized(reportedTerminalPosition.getColumn(), reportedTerminalPosition.getRow());
- return new TerminalSize(reportedTerminalPosition.getColumn(), reportedTerminalPosition.getRow());
- }
- }
- }
- finally {
- readLock.unlock();
- }
- }
-
- @Override
- public KeyStroke pollInput() throws IOException {
- return readInput(false);
- }
-
- @Override
- public KeyStroke readInput() throws IOException {
- return readInput(true);
- }
-
- private KeyStroke readInput(boolean blocking) throws IOException {
- readLock.lock();
- try {
- if(!keyQueue.isEmpty()) {
- return keyQueue.poll();
- }
- KeyStroke key = inputDecoder.getNextCharacter(blocking);
- if (key instanceof ScreenInfoAction) {
- TerminalPosition reportedTerminalPosition = ((ScreenInfoAction)key).getPosition();
- onResized(reportedTerminalPosition.getColumn(), reportedTerminalPosition.getRow());
- return pollInput();
- } else {
- return key;
- }
- }
- finally {
- readLock.unlock();
- }
- }
-
- @Override
- public void flush() throws IOException {
- synchronized(terminalOutput) {
- terminalOutput.flush();
- }
- }
-
- protected Charset getCharset() {
- return terminalCharset;
- }
-
- @SuppressWarnings("WeakerAccess")
- protected byte[] translateCharacter(char input) {
- if(UTF8_REFERENCE != null && UTF8_REFERENCE == terminalCharset) {
- return convertToCharset(input);
- }
- //Convert ACS to ordinary terminal codes
- switch(input) {
- case Symbols.ARROW_DOWN:
- return convertToVT100('v');
- case Symbols.ARROW_LEFT:
- return convertToVT100('<');
- case Symbols.ARROW_RIGHT:
- return convertToVT100('>');
- case Symbols.ARROW_UP:
- return convertToVT100('^');
- case Symbols.BLOCK_DENSE:
- case Symbols.BLOCK_MIDDLE:
- case Symbols.BLOCK_SOLID:
- case Symbols.BLOCK_SPARSE:
- return convertToVT100((char) 97);
- case Symbols.HEART:
- case Symbols.CLUB:
- case Symbols.SPADES:
- return convertToVT100('?');
- case Symbols.FACE_BLACK:
- case Symbols.FACE_WHITE:
- case Symbols.DIAMOND:
- return convertToVT100((char) 96);
- case Symbols.BULLET:
- return convertToVT100((char) 102);
- case Symbols.DOUBLE_LINE_CROSS:
- case Symbols.SINGLE_LINE_CROSS:
- return convertToVT100((char) 110);
- case Symbols.DOUBLE_LINE_HORIZONTAL:
- case Symbols.SINGLE_LINE_HORIZONTAL:
- return convertToVT100((char) 113);
- case Symbols.DOUBLE_LINE_BOTTOM_LEFT_CORNER:
- case Symbols.SINGLE_LINE_BOTTOM_LEFT_CORNER:
- return convertToVT100((char) 109);
- case Symbols.DOUBLE_LINE_BOTTOM_RIGHT_CORNER:
- case Symbols.SINGLE_LINE_BOTTOM_RIGHT_CORNER:
- return convertToVT100((char) 106);
- case Symbols.DOUBLE_LINE_T_DOWN:
- case Symbols.SINGLE_LINE_T_DOWN:
- case Symbols.DOUBLE_LINE_T_SINGLE_DOWN:
- case Symbols.SINGLE_LINE_T_DOUBLE_DOWN:
- return convertToVT100((char) 119);
- case Symbols.DOUBLE_LINE_T_LEFT:
- case Symbols.SINGLE_LINE_T_LEFT:
- case Symbols.DOUBLE_LINE_T_SINGLE_LEFT:
- case Symbols.SINGLE_LINE_T_DOUBLE_LEFT:
- return convertToVT100((char) 117);
- case Symbols.DOUBLE_LINE_T_RIGHT:
- case Symbols.SINGLE_LINE_T_RIGHT:
- case Symbols.DOUBLE_LINE_T_SINGLE_RIGHT:
- case Symbols.SINGLE_LINE_T_DOUBLE_RIGHT:
- return convertToVT100((char) 116);
- case Symbols.DOUBLE_LINE_T_UP:
- case Symbols.SINGLE_LINE_T_UP:
- case Symbols.DOUBLE_LINE_T_SINGLE_UP:
- case Symbols.SINGLE_LINE_T_DOUBLE_UP:
- return convertToVT100((char) 118);
- case Symbols.DOUBLE_LINE_TOP_LEFT_CORNER:
- case Symbols.SINGLE_LINE_TOP_LEFT_CORNER:
- return convertToVT100((char) 108);
- case Symbols.DOUBLE_LINE_TOP_RIGHT_CORNER:
- case Symbols.SINGLE_LINE_TOP_RIGHT_CORNER:
- return convertToVT100((char) 107);
- case Symbols.DOUBLE_LINE_VERTICAL:
- case Symbols.SINGLE_LINE_VERTICAL:
- return convertToVT100((char) 120);
- default:
- return convertToCharset(input);
- }
- }
-
- private byte[] convertToVT100(char code) {
- //Warning! This might be terminal type specific!!!!
- //So far it's worked everywhere I've tried it (xterm, gnome-terminal, putty)
- return new byte[]{27, 40, 48, (byte) code, 27, 40, 66};
- }
-
- private byte[] convertToCharset(char input) {
- return terminalCharset.encode(Character.toString(input)).array();
- }
-}