/* * 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-2012 Martin */ package com.googlecode.lanterna.terminal.text; import com.googlecode.lanterna.LanternaException; import com.googlecode.lanterna.input.InputDecoder; import com.googlecode.lanterna.terminal.ACS; import com.googlecode.lanterna.terminal.InputEnabledAbstractTerminal; 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; /** * An abstract terminal implementing functionality for terminals using * OutputStream/InputStream * @author Martin */ public abstract class StreamBasedTerminal extends InputEnabledAbstractTerminal { private static Charset UTF8_REFERENCE; static { try { UTF8_REFERENCE = Charset.forName("UTF-8"); } catch(Exception e) { UTF8_REFERENCE = null; } } private final OutputStream terminalOutput; private final Charset terminalCharset; protected final Object writerMutex; public StreamBasedTerminal(final InputStream terminalInput, final OutputStream terminalOutput, final Charset terminalCharset) { super(new InputDecoder(new InputStreamReader(terminalInput, terminalCharset))); this.writerMutex = new Object(); this.terminalOutput = terminalOutput; if(terminalCharset == null) this.terminalCharset = Charset.defaultCharset(); else this.terminalCharset = terminalCharset; } /** * Outputs a single character to the terminal output stream, translating any * UTF-8 graphical symbol if necessary * @param c Character to write to the output stream * @throws LanternaException */ @Override public void putCharacter(char c) { synchronized(writerMutex) { writeToTerminal(translateCharacter(c)); } } /** * Allow subclasses (that are supposed to know what they're doing) to write directly to the terminal<br> * Warning! Be sure to call this method INSIDE of a synchronize(writeMutex) block!!!<br> * The reason is that many control sequences are a group of bytes and we want to * synchronize the whole thing rather than each character one by one. */ protected void writeToTerminal(final byte... bytes) { try { terminalOutput.write(bytes); } catch (IOException e) { throw new LanternaException(e); } } @Override public void flush() { try { terminalOutput.flush(); } catch (IOException e) { throw new LanternaException(e); } } protected byte[] translateCharacter(char input) { if (UTF8_REFERENCE != null && UTF8_REFERENCE == terminalCharset) { return convertToCharset(input); } //Convert ACS to ordinary terminal codes switch (input) { case ACS.ARROW_DOWN: return convertToVT100('v'); case ACS.ARROW_LEFT: return convertToVT100('<'); case ACS.ARROW_RIGHT: return convertToVT100('>'); case ACS.ARROW_UP: return convertToVT100('^'); case ACS.BLOCK_DENSE: case ACS.BLOCK_MIDDLE: case ACS.BLOCK_SOLID: case ACS.BLOCK_SPARSE: return convertToVT100((char) 97); case ACS.HEART: case ACS.CLUB: case ACS.SPADES: return convertToVT100('?'); case ACS.FACE_BLACK: case ACS.FACE_WHITE: case ACS.DIAMOND: return convertToVT100((char) 96); case ACS.DOT: return convertToVT100((char) 102); case ACS.DOUBLE_LINE_CROSS: case ACS.SINGLE_LINE_CROSS: return convertToVT100((char) 110); case ACS.DOUBLE_LINE_HORIZONTAL: case ACS.SINGLE_LINE_HORIZONTAL: return convertToVT100((char) 113); case ACS.DOUBLE_LINE_LOW_LEFT_CORNER: case ACS.SINGLE_LINE_LOW_LEFT_CORNER: return convertToVT100((char) 109); case ACS.DOUBLE_LINE_LOW_RIGHT_CORNER: case ACS.SINGLE_LINE_LOW_RIGHT_CORNER: return convertToVT100((char) 106); case ACS.DOUBLE_LINE_T_DOWN: case ACS.SINGLE_LINE_T_DOWN: case ACS.DOUBLE_LINE_T_SINGLE_DOWN: case ACS.SINGLE_LINE_T_DOUBLE_DOWN: return convertToVT100((char) 119); case ACS.DOUBLE_LINE_T_LEFT: case ACS.SINGLE_LINE_T_LEFT: case ACS.DOUBLE_LINE_T_SINGLE_LEFT: case ACS.SINGLE_LINE_T_DOUBLE_LEFT: return convertToVT100((char) 117); case ACS.DOUBLE_LINE_T_RIGHT: case ACS.SINGLE_LINE_T_RIGHT: case ACS.DOUBLE_LINE_T_SINGLE_RIGHT: case ACS.SINGLE_LINE_T_DOUBLE_RIGHT: return convertToVT100((char) 116); case ACS.DOUBLE_LINE_T_UP: case ACS.SINGLE_LINE_T_UP: case ACS.DOUBLE_LINE_T_SINGLE_UP: case ACS.SINGLE_LINE_T_DOUBLE_UP: return convertToVT100((char) 118); case ACS.DOUBLE_LINE_UP_LEFT_CORNER: case ACS.SINGLE_LINE_UP_LEFT_CORNER: return convertToVT100((char) 108); case ACS.DOUBLE_LINE_UP_RIGHT_CORNER: case ACS.SINGLE_LINE_UP_RIGHT_CORNER: return convertToVT100((char) 107); case ACS.DOUBLE_LINE_VERTICAL: case ACS.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) { //TODO: This is a silly way to do it, improve? final char[] buffer = new char[1]; buffer[0] = input; return terminalCharset.encode(CharBuffer.wrap(buffer)).array(); } }