/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2017 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @author Kevin Lamonte [kevin.lamonte@gmail.com]
* @version 1
*/
package jexer.tterminal;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import jexer.TKeypress;
import jexer.event.TMouseEvent;
import jexer.bits.Color;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.io.ReadTimeoutException;
import jexer.io.TimeoutInputStream;
import static jexer.TKeypress.*;
/**
* This implements a complex ECMA-48/ISO 6429/ANSI X3.64 type console,
* including a scrollback buffer.
*
*
* It currently implements VT100, VT102, VT220, and XTERM with the following
* caveats:
*
*
* - The vttest scenario for VT220 8-bit controls (11.1.2.3) reports a
* failure with XTERM. This is due to vttest failing to decode the UTF-8
* stream.
*
*
* - Smooth scrolling, printing, keyboard locking, keyboard leds, and tests
* from VT100 are not supported.
*
*
* - User-defined keys (DECUDK), downloadable fonts (DECDLD), and VT100/ANSI
* compatibility mode (DECSCL) from VT220 are not supported. (Also,
* because DECSCL is not supported, it will fail the last part of the
* vttest "Test of VT52 mode" if DeviceType is set to VT220.)
*
*
* - Numeric/application keys from the number pad are not supported because
* they are not exposed from the TKeypress API.
*
*
* - VT52 HOLD SCREEN mode is not supported.
*
*
* - In VT52 graphics mode, the 3/, 5/, and 7/ characters (fraction
* numerators) are not rendered correctly.
*
*
* - All data meant for the 'printer' (CSI Pc ? i) is discarded.
*/
public class ECMA48 implements Runnable {
/**
* The emulator can emulate several kinds of terminals.
*/
public enum DeviceType {
/**
* DEC VT100 but also including the three VT102 functions.
*/
VT100,
/**
* DEC VT102.
*/
VT102,
/**
* DEC VT220.
*/
VT220,
/**
* A subset of xterm.
*/
XTERM
}
/**
* Return the proper primary Device Attributes string.
*
* @return string to send to remote side that is appropriate for the
* this.type
*/
private String deviceTypeResponse() {
switch (type) {
case VT100:
// "I am a VT100 with advanced video option" (often VT102)
return "\033[?1;2c";
case VT102:
// "I am a VT102"
return "\033[?6c";
case VT220:
case XTERM:
// "I am a VT220" - 7 bit version
if (!s8c1t) {
return "\033[?62;1;6c";
}
// "I am a VT220" - 8 bit version
return "\u009b?62;1;6c";
default:
throw new IllegalArgumentException("Invalid device type: " + type);
}
}
/**
* Return the proper TERM environment variable for this device type.
*
* @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
* @return "vt100", "xterm", etc.
*/
public static String deviceTypeTerm(final DeviceType deviceType) {
switch (deviceType) {
case VT100:
return "vt100";
case VT102:
return "vt102";
case VT220:
return "vt220";
case XTERM:
return "xterm";
default:
throw new IllegalArgumentException("Invalid device type: "
+ deviceType);
}
}
/**
* Return the proper LANG for this device type. Only XTERM devices know
* about UTF-8, the others are defined by their standard to be either
* 7-bit or 8-bit characters only.
*
* @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
* @param baseLang a base language without UTF-8 flag such as "C" or
* "en_US"
* @return "en_US", "en_US.UTF-8", etc.
*/
public static String deviceTypeLang(final DeviceType deviceType,
final String baseLang) {
switch (deviceType) {
case VT100:
case VT102:
case VT220:
return baseLang;
case XTERM:
return baseLang + ".UTF-8";
default:
throw new IllegalArgumentException("Invalid device type: "
+ deviceType);
}
}
/**
* Write a string directly to the remote side.
*
* @param str string to send
*/
public void writeRemote(final String str) {
if (stopReaderThread) {
// Reader hit EOF, bail out now.
close();
return;
}
// System.err.printf("writeRemote() '%s'\n", str);
switch (type) {
case VT100:
case VT102:
case VT220:
if (outputStream == null) {
return;
}
try {
outputStream.flush();
for (int i = 0; i < str.length(); i++) {
outputStream.write(str.charAt(i));
}
outputStream.flush();
} catch (IOException e) {
// Assume EOF
close();
}
break;
case XTERM:
if (output == null) {
return;
}
try {
output.flush();
output.write(str);
output.flush();
} catch (IOException e) {
// Assume EOF
close();
}
break;
default:
throw new IllegalArgumentException("Invalid device type: " + type);
}
}
/**
* Close the input and output streams and stop the reader thread. Note
* that it is safe to call this multiple times.
*/
public final void close() {
// Tell the reader thread to stop looking at input. It will close
// the input streams.
if (stopReaderThread == false) {
stopReaderThread = true;
try {
readerThread.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Now close the output stream.
switch (type) {
case VT100:
case VT102:
case VT220:
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
// SQUASH
}
outputStream = null;
}
break;
case XTERM:
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
// SQUASH
}
outputStream = null;
}
if (output != null) {
try {
output.close();
} catch (IOException e) {
// SQUASH
}
output = null;
}
break;
default:
throw new IllegalArgumentException("Invalid device type: " +
type);
}
}
/**
* The enclosing listening object.
*/
private DisplayListener displayListener;
/**
* When true, the reader thread is expected to exit.
*/
private volatile boolean stopReaderThread = false;
/**
* The reader thread.
*/
private Thread readerThread = null;
/**
* See if the reader thread is still running.
*
* @return if true, we are still connected to / reading from the remote
* side
*/
public final boolean isReading() {
return (!stopReaderThread);
}
/**
* The type of emulator to be.
*/
private DeviceType type = DeviceType.VT102;
/**
* Obtain a new blank display line for an external user
* (e.g. TTerminalWindow).
*
* @return new blank line
*/
public final DisplayLine getBlankDisplayLine() {
return new DisplayLine(currentState.attr);
}
/**
* The scrollback buffer characters + attributes.
*/
private volatile List scrollback;
/**
* Get the scrollback buffer.
*
* @return the scrollback buffer
*/
public final List getScrollbackBuffer() {
return scrollback;
}
/**
* The raw display buffer characters + attributes.
*/
private volatile List display;
/**
* Get the display buffer.
*
* @return the display buffer
*/
public final List getDisplayBuffer() {
return display;
}
/**
* The terminal's input. For type == XTERM, this is an InputStreamReader
* with UTF-8 encoding.
*/
private Reader input;
/**
* The terminal's raw InputStream. This is used for type != XTERM.
*/
private volatile TimeoutInputStream inputStream;
/**
* The terminal's output. For type == XTERM, this wraps an
* OutputStreamWriter with UTF-8 encoding.
*/
private Writer output;
/**
* The terminal's raw OutputStream. This is used for type != XTERM.
*/
private OutputStream outputStream;
/**
* Parser character scan states.
*/
enum ScanState {
GROUND,
ESCAPE,
ESCAPE_INTERMEDIATE,
CSI_ENTRY,
CSI_PARAM,
CSI_INTERMEDIATE,
CSI_IGNORE,
DCS_ENTRY,
DCS_INTERMEDIATE,
DCS_PARAM,
DCS_PASSTHROUGH,
DCS_IGNORE,
SOSPMAPC_STRING,
OSC_STRING,
VT52_DIRECT_CURSOR_ADDRESS
}
/**
* Current scanning state.
*/
private ScanState scanState;
/**
* The selected number pad mode (DECKPAM, DECKPNM). We record this, but
* can't really use it in keypress() because we do not see number pad
* events from TKeypress.
*/
private enum KeypadMode {
Application,
Numeric
}
/**
* Arrow keys can emit three different sequences (DECCKM or VT52
* submode).
*/
private enum ArrowKeyMode {
VT52,
ANSI,
VT100
}
/**
* Available character sets for GL, GR, G0, G1, G2, G3.
*/
private enum CharacterSet {
US,
UK,
DRAWING,
ROM,
ROM_SPECIAL,
VT52_GRAPHICS,
DEC_SUPPLEMENTAL,
NRC_DUTCH,
NRC_FINNISH,
NRC_FRENCH,
NRC_FRENCH_CA,
NRC_GERMAN,
NRC_ITALIAN,
NRC_NORWEGIAN,
NRC_SPANISH,
NRC_SWEDISH,
NRC_SWISS
}
/**
* Single-shift states used by the C1 control characters SS2 (0x8E) and
* SS3 (0x8F).
*/
private enum Singleshift {
NONE,
SS2,
SS3
}
/**
* VT220+ lockshift states.
*/
private enum LockshiftMode {
NONE,
G1_GR,
G2_GR,
G2_GL,
G3_GR,
G3_GL
}
/**
* XTERM mouse reporting protocols.
*/
private enum MouseProtocol {
OFF,
X10,
NORMAL,
BUTTONEVENT,
ANYEVENT
}
/**
* Which mouse protocol is active.
*/
private MouseProtocol mouseProtocol = MouseProtocol.OFF;
/**
* XTERM mouse reporting encodings.
*/
private enum MouseEncoding {
X10,
UTF8,
SGR
}
/**
* Which mouse encoding is active.
*/
private MouseEncoding mouseEncoding = MouseEncoding.X10;
/**
* Physical display width. We start at 80x24, but the user can resize us
* bigger/smaller.
*/
private int width;
/**
* Get the display width.
*
* @return the width (usually 80 or 132)
*/
public final int getWidth() {
return width;
}
/**
* Set the display width.
*
* @param width the new width
*/
public final void setWidth(final int width) {
this.width = width;
rightMargin = width - 1;
if (currentState.cursorX >= width) {
currentState.cursorX = width - 1;
}
if (savedState.cursorX >= width) {
savedState.cursorX = width - 1;
}
}
/**
* Physical display height. We start at 80x24, but the user can resize
* us bigger/smaller.
*/
private int height;
/**
* Get the display height.
*
* @return the height (usually 24)
*/
public final int getHeight() {
return height;
}
/**
* Set the display height.
*
* @param height the new height
*/
public final void setHeight(final int height) {
int delta = height - this.height;
this.height = height;
scrollRegionBottom += delta;
if (scrollRegionBottom < 0) {
scrollRegionBottom = height;
}
if (scrollRegionTop >= scrollRegionBottom) {
scrollRegionTop = 0;
}
if (currentState.cursorY >= height) {
currentState.cursorY = height - 1;
}
if (savedState.cursorY >= height) {
savedState.cursorY = height - 1;
}
while (display.size() < height) {
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
display.add(line);
}
while (display.size() > height) {
scrollback.add(display.remove(0));
}
}
/**
* Top margin of the scrolling region.
*/
private int scrollRegionTop;
/**
* Bottom margin of the scrolling region.
*/
private int scrollRegionBottom;
/**
* Right margin column number. This can be selected by the remote side
* to be 80/132 (rightMargin values 79/131), or it can be (width - 1).
*/
private int rightMargin;
/**
* Last character printed.
*/
private char repCh;
/**
* VT100-style line wrapping: a character is placed in column 80 (or
* 132), but the line does NOT wrap until another character is written to
* column 1 of the next line, after which the cursor moves to column 2.
*/
private boolean wrapLineFlag;
/**
* VT220 single shift flag.
*/
private Singleshift singleshift = Singleshift.NONE;
/**
* true = insert characters, false = overwrite.
*/
private boolean insertMode = false;
/**
* VT52 mode as selected by DECANM. True means VT52, false means
* ANSI. Default is ANSI.
*/
private boolean vt52Mode = false;
/**
* Visible cursor (DECTCEM).
*/
private boolean cursorVisible = true;
/**
* Get visible cursor flag.
*
* @return if true, the cursor is visible
*/
public final boolean isCursorVisible() {
return cursorVisible;
}
/**
* Screen title as set by the xterm OSC sequence. Lots of applications
* send a screenTitle regardless of whether it is an xterm client or not.
*/
private String screenTitle = "";
/**
* Get the screen title as set by the xterm OSC sequence. Lots of
* applications send a screenTitle regardless of whether it is an xterm
* client or not.
*
* @return screen title
*/
public final String getScreenTitle() {
return screenTitle;
}
/**
* Parameter characters being collected.
*/
private List csiParams;
/**
* Non-csi collect buffer.
*/
private StringBuilder collectBuffer;
/**
* When true, use the G1 character set.
*/
private boolean shiftOut = false;
/**
* Horizontal tab stop locations.
*/
private List tabStops;
/**
* S8C1T. True means 8bit controls, false means 7bit controls.
*/
private boolean s8c1t = false;
/**
* Printer mode. True means send all output to printer, which discards
* it.
*/
private boolean printerControllerMode = false;
/**
* LMN line mode. If true, linefeed() puts the cursor on the first
* column of the next line. If false, linefeed() puts the cursor one
* line down on the current line. The default is false.
*/
private boolean newLineMode = false;
/**
* Whether arrow keys send ANSI, VT100, or VT52 sequences.
*/
private ArrowKeyMode arrowKeyMode;
/**
* Whether number pad keys send VT100 or VT52, application or numeric
* sequences.
*/
@SuppressWarnings("unused")
private KeypadMode keypadMode;
/**
* When true, the terminal is in 132-column mode (DECCOLM).
*/
private boolean columns132 = false;
/**
* Get 132 columns value.
*
* @return if true, the terminal is in 132 column mode
*/
public final boolean isColumns132() {
return columns132;
}
/**
* true = reverse video. Set by DECSCNM.
*/
private boolean reverseVideo = false;
/**
* false = echo characters locally.
*/
private boolean fullDuplex = true;
/**
* DECSC/DECRC save/restore a subset of the total state. This class
* encapsulates those specific flags/modes.
*/
private class SaveableState {
/**
* When true, cursor positions are relative to the scrolling region.
*/
public boolean originMode = false;
/**
* The current editing X position.
*/
public int cursorX = 0;
/**
* The current editing Y position.
*/
public int cursorY = 0;
/**
* Which character set is currently selected in G0.
*/
public CharacterSet g0Charset = CharacterSet.US;
/**
* Which character set is currently selected in G1.
*/
public CharacterSet g1Charset = CharacterSet.DRAWING;
/**
* Which character set is currently selected in G2.
*/
public CharacterSet g2Charset = CharacterSet.US;
/**
* Which character set is currently selected in G3.
*/
public CharacterSet g3Charset = CharacterSet.US;
/**
* Which character set is currently selected in GR.
*/
public CharacterSet grCharset = CharacterSet.DRAWING;
/**
* The current drawing attributes.
*/
public CellAttributes attr;
/**
* GL lockshift mode.
*/
public LockshiftMode glLockshift = LockshiftMode.NONE;
/**
* GR lockshift mode.
*/
public LockshiftMode grLockshift = LockshiftMode.NONE;
/**
* Line wrap.
*/
public boolean lineWrap = true;
/**
* Reset to defaults.
*/
public void reset() {
originMode = false;
cursorX = 0;
cursorY = 0;
g0Charset = CharacterSet.US;
g1Charset = CharacterSet.DRAWING;
g2Charset = CharacterSet.US;
g3Charset = CharacterSet.US;
grCharset = CharacterSet.DRAWING;
attr = new CellAttributes();
glLockshift = LockshiftMode.NONE;
grLockshift = LockshiftMode.NONE;
lineWrap = true;
}
/**
* Copy attributes from another instance.
*
* @param that the other instance to match
*/
public void setTo(final SaveableState that) {
this.originMode = that.originMode;
this.cursorX = that.cursorX;
this.cursorY = that.cursorY;
this.g0Charset = that.g0Charset;
this.g1Charset = that.g1Charset;
this.g2Charset = that.g2Charset;
this.g3Charset = that.g3Charset;
this.grCharset = that.grCharset;
this.attr = new CellAttributes();
this.attr.setTo(that.attr);
this.glLockshift = that.glLockshift;
this.grLockshift = that.grLockshift;
this.lineWrap = that.lineWrap;
}
/**
* Public constructor.
*/
public SaveableState() {
reset();
}
}
/**
* The current terminal state.
*/
private SaveableState currentState;
/**
* The last saved terminal state.
*/
private SaveableState savedState;
/**
* Clear the CSI parameters and flags.
*/
private void toGround() {
csiParams.clear();
collectBuffer = new StringBuilder(8);
scanState = ScanState.GROUND;
}
/**
* Reset the tab stops list.
*/
private void resetTabStops() {
tabStops.clear();
for (int i = 0; (i * 8) <= rightMargin; i++) {
tabStops.add(new Integer(i * 8));
}
}
/**
* Reset the emulation state.
*/
private void reset() {
currentState = new SaveableState();
savedState = new SaveableState();
scanState = ScanState.GROUND;
width = 80;
height = 24;
scrollRegionTop = 0;
scrollRegionBottom = height - 1;
rightMargin = width - 1;
newLineMode = false;
arrowKeyMode = ArrowKeyMode.ANSI;
keypadMode = KeypadMode.Numeric;
wrapLineFlag = false;
if (displayListener != null) {
width = displayListener.getDisplayWidth();
height = displayListener.getDisplayHeight();
rightMargin = width - 1;
}
// Flags
shiftOut = false;
vt52Mode = false;
insertMode = false;
columns132 = false;
newLineMode = false;
reverseVideo = false;
fullDuplex = true;
cursorVisible = true;
// VT220
singleshift = Singleshift.NONE;
s8c1t = false;
printerControllerMode = false;
// XTERM
mouseProtocol = MouseProtocol.OFF;
mouseEncoding = MouseEncoding.X10;
// Tab stops
resetTabStops();
// Clear CSI stuff
toGround();
}
/**
* Public constructor.
*
* @param type one of the DeviceType constants to select VT100, VT102,
* VT220, or XTERM
* @param inputStream an InputStream connected to the remote side. For
* type == XTERM, inputStream is converted to a Reader with UTF-8
* encoding.
* @param outputStream an OutputStream connected to the remote user. For
* type == XTERM, outputStream is converted to a Writer with UTF-8
* encoding.
* @param displayListener a callback to the outer display, or null for
* default VT100 behavior
* @throws UnsupportedEncodingException if an exception is thrown when
* creating the InputStreamReader
*/
public ECMA48(final DeviceType type, final InputStream inputStream,
final OutputStream outputStream, final DisplayListener displayListener)
throws UnsupportedEncodingException {
assert (inputStream != null);
assert (outputStream != null);
csiParams = new ArrayList();
tabStops = new ArrayList();
scrollback = new LinkedList();
display = new LinkedList();
this.type = type;
if (inputStream instanceof TimeoutInputStream) {
this.inputStream = (TimeoutInputStream)inputStream;
} else {
this.inputStream = new TimeoutInputStream(inputStream, 2000);
}
if (type == DeviceType.XTERM) {
this.input = new InputStreamReader(this.inputStream, "UTF-8");
this.output = new OutputStreamWriter(new
BufferedOutputStream(outputStream), "UTF-8");
this.outputStream = null;
} else {
this.output = null;
this.outputStream = new BufferedOutputStream(outputStream);
}
this.displayListener = displayListener;
reset();
for (int i = 0; i < height; i++) {
display.add(new DisplayLine(currentState.attr));
}
// Spin up the input reader
readerThread = new Thread(this);
readerThread.start();
}
/**
* Append a new line to the bottom of the display, adding lines off the
* top to the scrollback buffer.
*/
private void newDisplayLine() {
// Scroll the top line off into the scrollback buffer
scrollback.add(display.get(0));
display.remove(0);
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
display.add(line);
}
/**
* Wraps the current line.
*/
private void wrapCurrentLine() {
if (currentState.cursorY == height - 1) {
newDisplayLine();
}
if (currentState.cursorY < height - 1) {
currentState.cursorY++;
}
currentState.cursorX = 0;
}
/**
* Handle a carriage return.
*/
private void carriageReturn() {
currentState.cursorX = 0;
wrapLineFlag = false;
}
/**
* Reverse the color of the visible display.
*/
private void invertDisplayColors() {
for (DisplayLine line: display) {
line.setReverseColor(!line.isReverseColor());
}
}
/**
* Handle a linefeed.
*/
private void linefeed() {
if (currentState.cursorY < scrollRegionBottom) {
// Increment screen y
currentState.cursorY++;
} else {
// Screen y does not increment
/*
* Two cases: either we're inside a scrolling region or not. If
* the scrolling region bottom is the bottom of the screen, then
* push the top line into the buffer. Else scroll the scrolling
* region up.
*/
if ((scrollRegionBottom == height - 1) && (scrollRegionTop == 0)) {
// We're at the bottom of the scroll region, AND the scroll
// region is the entire screen.
// New line
newDisplayLine();
} else {
// We're at the bottom of the scroll region, AND the scroll
// region is NOT the entire screen.
scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
}
}
if (newLineMode) {
currentState.cursorX = 0;
}
wrapLineFlag = false;
}
/**
* Prints one character to the display buffer.
*
* @param ch character to display
*/
private void printCharacter(final char ch) {
int rightMargin = this.rightMargin;
// Check if we have double-width, and if so chop at 40/66 instead of
// 80/132
if (display.get(currentState.cursorY).isDoubleWidth()) {
rightMargin = ((rightMargin + 1) / 2) - 1;
}
// Check the unusually-complicated line wrapping conditions...
if (currentState.cursorX == rightMargin) {
if (currentState.lineWrap == true) {
/*
* This case happens when: the cursor was already on the
* right margin (either through printing or by an explicit
* placement command), and a character was printed.
*
* The line wraps only when a new character arrives AND the
* cursor is already on the right margin AND has placed a
* character in its cell. Easier to see than to explain.
*/
if (wrapLineFlag == false) {
/*
* This block marks the case that we are in the margin
* and the first character has been received and printed.
*/
wrapLineFlag = true;
} else {
/*
* This block marks the case that we are in the margin
* and the second character has been received and
* printed.
*/
wrapLineFlag = false;
wrapCurrentLine();
}
}
} else if (currentState.cursorX <= rightMargin) {
/*
* This is the normal case: a character came in and was printed
* to the left of the right margin column.
*/
// Turn off VT100 special-case flag
wrapLineFlag = false;
}
// "Print" the character
Cell newCell = new Cell(ch);
CellAttributes newCellAttributes = (CellAttributes) newCell;
newCellAttributes.setTo(currentState.attr);
DisplayLine line = display.get(currentState.cursorY);
// Insert mode special case
if (insertMode == true) {
line.insert(currentState.cursorX, newCell);
} else {
// Replace an existing character
line.replace(currentState.cursorX, newCell);
}
// Increment horizontal
if (wrapLineFlag == false) {
currentState.cursorX++;
if (currentState.cursorX > rightMargin) {
currentState.cursorX--;
}
}
}
/**
* Translate the mouse event to a VT100, VT220, or XTERM sequence and
* send to the remote side.
*
* @param mouse mouse event received from the local user
*/
public void mouse(final TMouseEvent mouse) {
/*
System.err.printf("mouse(): protocol %s encoding %s mouse %s\n",
mouseProtocol, mouseEncoding, mouse);
*/
if (mouseEncoding == MouseEncoding.X10) {
// We will support X10 but only for (160,94) and smaller.
if ((mouse.getX() >= 160) || (mouse.getY() >= 94)) {
return;
}
}
switch (mouseProtocol) {
case OFF:
// Do nothing
return;
case X10:
// Only report button presses
if (mouse.getType() != TMouseEvent.Type.MOUSE_DOWN) {
return;
}
break;
case NORMAL:
// Only report button presses and releases
if ((mouse.getType() != TMouseEvent.Type.MOUSE_DOWN)
&& (mouse.getType() != TMouseEvent.Type.MOUSE_UP)
) {
return;
}
break;
case BUTTONEVENT:
/*
* Only report button presses, button releases, and motions that
* have a button down (i.e. drag-and-drop).
*/
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
if (!mouse.isMouse1()
&& !mouse.isMouse2()
&& !mouse.isMouse3()
&& !mouse.isMouseWheelUp()
&& !mouse.isMouseWheelDown()
) {
return;
}
}
break;
case ANYEVENT:
// Report everything
break;
}
// Now encode the event
StringBuilder sb = new StringBuilder(6);
if (mouseEncoding == MouseEncoding.SGR) {
sb.append((char) 0x1B);
sb.append("[<");
if (mouse.isMouse1()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
sb.append("32;");
} else {
sb.append("0;");
}
} else if (mouse.isMouse2()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
sb.append("33;");
} else {
sb.append("1;");
}
} else if (mouse.isMouse3()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
sb.append("34;");
} else {
sb.append("2;");
}
} else if (mouse.isMouseWheelUp()) {
sb.append("64;");
} else if (mouse.isMouseWheelDown()) {
sb.append("65;");
} else {
// This is motion with no buttons down.
sb.append("35;");
}
sb.append(String.format("%d;%d", mouse.getX() + 1,
mouse.getY() + 1));
if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
sb.append("m");
} else {
sb.append("M");
}
} else {
// X10 and UTF8 encodings
sb.append((char) 0x1B);
sb.append('[');
sb.append('M');
if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
sb.append((char) (0x03 + 32));
} else if (mouse.isMouse1()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
sb.append((char) (0x00 + 32 + 32));
} else {
sb.append((char) (0x00 + 32));
}
} else if (mouse.isMouse2()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
sb.append((char) (0x01 + 32 + 32));
} else {
sb.append((char) (0x01 + 32));
}
} else if (mouse.isMouse3()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
sb.append((char) (0x02 + 32 + 32));
} else {
sb.append((char) (0x02 + 32));
}
} else if (mouse.isMouseWheelUp()) {
sb.append((char) (0x04 + 64));
} else if (mouse.isMouseWheelDown()) {
sb.append((char) (0x05 + 64));
} else {
// This is motion with no buttons down.
sb.append((char) (0x03 + 32));
}
sb.append((char) (mouse.getX() + 33));
sb.append((char) (mouse.getY() + 33));
}
// System.err.printf("Would write: \'%s\'\n", sb.toString());
writeRemote(sb.toString());
}
/**
* Translate the keyboard press to a VT100, VT220, or XTERM sequence and
* send to the remote side.
*
* @param keypress keypress received from the local user
*/
public void keypress(final TKeypress keypress) {
writeRemote(keypressToString(keypress));
}
/**
* Build one of the complex xterm keystroke sequences, storing the result in
* xterm_keystroke_buffer.
*
* @param ss3 the prefix to use based on VT100 state.
* @param first the first character, usually a number.
* @param first the last character, one of the following: ~ A B C D F H
* @param ctrl whether or not ctrl is down
* @param alt whether or not alt is down
* @param shift whether or not shift is down
* @return the buffer with the full key sequence
*/
private String xtermBuildKeySequence(final String ss3, final char first,
final char last, boolean ctrl, boolean alt, boolean shift) {
StringBuilder sb = new StringBuilder(ss3);
if ((last == '~') || (ctrl == true) || (alt == true)
|| (shift == true)
) {
sb.append(first);
if ( (ctrl == false) && (alt == false) && (shift == true)) {
sb.append(";2");
} else if ((ctrl == false) && (alt == true) && (shift == false)) {
sb.append(";3");
} else if ((ctrl == false) && (alt == true) && (shift == true)) {
sb.append(";4");
} else if ((ctrl == true) && (alt == false) && (shift == false)) {
sb.append(";5");
} else if ((ctrl == true) && (alt == false) && (shift == true)) {
sb.append(";6");
} else if ((ctrl == true) && (alt == true) && (shift == false)) {
sb.append(";7");
} else if ((ctrl == true) && (alt == true) && (shift == true)) {
sb.append(";8");
}
}
sb.append(last);
return sb.toString();
}
/**
* Translate the keyboard press to a VT100, VT220, or XTERM sequence.
*
* @param keypress keypress received from the local user
* @return string to transmit to the remote side
*/
private String keypressToString(final TKeypress keypress) {
if ((fullDuplex == false) && (!keypress.isFnKey())) {
/*
* If this is a control character, process it like it came from
* the remote side.
*/
if (keypress.getChar() < 0x20) {
handleControlChar(keypress.getChar());
} else {
// Local echo for everything else
printCharacter(keypress.getChar());
}
}
if ((newLineMode == true) && (keypress.equals(kbEnter))) {
// NLM: send CRLF
return "\015\012";
}
// Handle control characters
if ((keypress.isCtrl()) && (!keypress.isFnKey())) {
StringBuilder sb = new StringBuilder();
char ch = keypress.getChar();
ch -= 0x40;
sb.append(ch);
return sb.toString();
}
// Handle alt characters
if ((keypress.isAlt()) && (!keypress.isFnKey())) {
StringBuilder sb = new StringBuilder("\033");
char ch = keypress.getChar();
sb.append(ch);
return sb.toString();
}
if (keypress.equals(kbBackspace)) {
switch (type) {
case VT100:
return "\010";
case VT102:
return "\010";
case VT220:
return "\177";
case XTERM:
return "\177";
}
}
if (keypress.equalsWithoutModifiers(kbLeft)) {
switch (type) {
case XTERM:
switch (arrowKeyMode) {
case ANSI:
return xtermBuildKeySequence("\033[", '1', 'D',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT52:
return xtermBuildKeySequence("\033", '1', 'D',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT100:
return xtermBuildKeySequence("\033O", '1', 'D',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
}
default:
switch (arrowKeyMode) {
case ANSI:
return "\033[D";
case VT52:
return "\033D";
case VT100:
return "\033OD";
}
}
}
if (keypress.equalsWithoutModifiers(kbRight)) {
switch (type) {
case XTERM:
switch (arrowKeyMode) {
case ANSI:
return xtermBuildKeySequence("\033[", '1', 'C',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT52:
return xtermBuildKeySequence("\033", '1', 'C',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT100:
return xtermBuildKeySequence("\033O", '1', 'C',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
}
default:
switch (arrowKeyMode) {
case ANSI:
return "\033[C";
case VT52:
return "\033C";
case VT100:
return "\033OC";
}
}
}
if (keypress.equalsWithoutModifiers(kbUp)) {
switch (type) {
case XTERM:
switch (arrowKeyMode) {
case ANSI:
return xtermBuildKeySequence("\033[", '1', 'A',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT52:
return xtermBuildKeySequence("\033", '1', 'A',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT100:
return xtermBuildKeySequence("\033O", '1', 'A',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
}
default:
switch (arrowKeyMode) {
case ANSI:
return "\033[A";
case VT52:
return "\033A";
case VT100:
return "\033OA";
}
}
}
if (keypress.equalsWithoutModifiers(kbDown)) {
switch (type) {
case XTERM:
switch (arrowKeyMode) {
case ANSI:
return xtermBuildKeySequence("\033[", '1', 'B',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT52:
return xtermBuildKeySequence("\033", '1', 'B',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT100:
return xtermBuildKeySequence("\033O", '1', 'B',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
}
default:
switch (arrowKeyMode) {
case ANSI:
return "\033[B";
case VT52:
return "\033B";
case VT100:
return "\033OB";
}
}
}
if (keypress.equalsWithoutModifiers(kbHome)) {
switch (type) {
case XTERM:
switch (arrowKeyMode) {
case ANSI:
return xtermBuildKeySequence("\033[", '1', 'H',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT52:
return xtermBuildKeySequence("\033", '1', 'H',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT100:
return xtermBuildKeySequence("\033O", '1', 'H',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
}
default:
switch (arrowKeyMode) {
case ANSI:
return "\033[H";
case VT52:
return "\033H";
case VT100:
return "\033OH";
}
}
}
if (keypress.equalsWithoutModifiers(kbEnd)) {
switch (type) {
case XTERM:
switch (arrowKeyMode) {
case ANSI:
return xtermBuildKeySequence("\033[", '1', 'F',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT52:
return xtermBuildKeySequence("\033", '1', 'F',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
case VT100:
return xtermBuildKeySequence("\033O", '1', 'F',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
}
default:
switch (arrowKeyMode) {
case ANSI:
return "\033[F";
case VT52:
return "\033F";
case VT100:
return "\033OF";
}
}
}
if (keypress.equals(kbF1)) {
// PF1
if (vt52Mode) {
return "\033P";
}
return "\033OP";
}
if (keypress.equals(kbF2)) {
// PF2
if (vt52Mode) {
return "\033Q";
}
return "\033OQ";
}
if (keypress.equals(kbF3)) {
// PF3
if (vt52Mode) {
return "\033R";
}
return "\033OR";
}
if (keypress.equals(kbF4)) {
// PF4
if (vt52Mode) {
return "\033S";
}
return "\033OS";
}
if (keypress.equals(kbF5)) {
switch (type) {
case VT100:
return "\033Ot";
case VT102:
return "\033Ot";
case VT220:
return "\033[15~";
case XTERM:
return "\033[15~";
}
}
if (keypress.equals(kbF6)) {
switch (type) {
case VT100:
return "\033Ou";
case VT102:
return "\033Ou";
case VT220:
return "\033[17~";
case XTERM:
return "\033[17~";
}
}
if (keypress.equals(kbF7)) {
switch (type) {
case VT100:
return "\033Ov";
case VT102:
return "\033Ov";
case VT220:
return "\033[18~";
case XTERM:
return "\033[18~";
}
}
if (keypress.equals(kbF8)) {
switch (type) {
case VT100:
return "\033Ol";
case VT102:
return "\033Ol";
case VT220:
return "\033[19~";
case XTERM:
return "\033[19~";
}
}
if (keypress.equals(kbF9)) {
switch (type) {
case VT100:
return "\033Ow";
case VT102:
return "\033Ow";
case VT220:
return "\033[20~";
case XTERM:
return "\033[20~";
}
}
if (keypress.equals(kbF10)) {
switch (type) {
case VT100:
return "\033Ox";
case VT102:
return "\033Ox";
case VT220:
return "\033[21~";
case XTERM:
return "\033[21~";
}
}
if (keypress.equals(kbF11)) {
return "\033[23~";
}
if (keypress.equals(kbF12)) {
return "\033[24~";
}
if (keypress.equals(kbShiftF1)) {
// Shifted PF1
if (vt52Mode) {
return "\0332P";
}
if (type == DeviceType.XTERM) {
return "\0331;2P";
}
return "\033O2P";
}
if (keypress.equals(kbShiftF2)) {
// Shifted PF2
if (vt52Mode) {
return "\0332Q";
}
if (type == DeviceType.XTERM) {
return "\0331;2Q";
}
return "\033O2Q";
}
if (keypress.equals(kbShiftF3)) {
// Shifted PF3
if (vt52Mode) {
return "\0332R";
}
if (type == DeviceType.XTERM) {
return "\0331;2R";
}
return "\033O2R";
}
if (keypress.equals(kbShiftF4)) {
// Shifted PF4
if (vt52Mode) {
return "\0332S";
}
if (type == DeviceType.XTERM) {
return "\0331;2S";
}
return "\033O2S";
}
if (keypress.equals(kbShiftF5)) {
// Shifted F5
return "\033[15;2~";
}
if (keypress.equals(kbShiftF6)) {
// Shifted F6
return "\033[17;2~";
}
if (keypress.equals(kbShiftF7)) {
// Shifted F7
return "\033[18;2~";
}
if (keypress.equals(kbShiftF8)) {
// Shifted F8
return "\033[19;2~";
}
if (keypress.equals(kbShiftF9)) {
// Shifted F9
return "\033[20;2~";
}
if (keypress.equals(kbShiftF10)) {
// Shifted F10
return "\033[21;2~";
}
if (keypress.equals(kbShiftF11)) {
// Shifted F11
return "\033[23;2~";
}
if (keypress.equals(kbShiftF12)) {
// Shifted F12
return "\033[24;2~";
}
if (keypress.equals(kbCtrlF1)) {
// Control PF1
if (vt52Mode) {
return "\0335P";
}
if (type == DeviceType.XTERM) {
return "\0331;5P";
}
return "\033O5P";
}
if (keypress.equals(kbCtrlF2)) {
// Control PF2
if (vt52Mode) {
return "\0335Q";
}
if (type == DeviceType.XTERM) {
return "\0331;5Q";
}
return "\033O5Q";
}
if (keypress.equals(kbCtrlF3)) {
// Control PF3
if (vt52Mode) {
return "\0335R";
}
if (type == DeviceType.XTERM) {
return "\0331;5R";
}
return "\033O5R";
}
if (keypress.equals(kbCtrlF4)) {
// Control PF4
if (vt52Mode) {
return "\0335S";
}
if (type == DeviceType.XTERM) {
return "\0331;5S";
}
return "\033O5S";
}
if (keypress.equals(kbCtrlF5)) {
// Control F5
return "\033[15;5~";
}
if (keypress.equals(kbCtrlF6)) {
// Control F6
return "\033[17;5~";
}
if (keypress.equals(kbCtrlF7)) {
// Control F7
return "\033[18;5~";
}
if (keypress.equals(kbCtrlF8)) {
// Control F8
return "\033[19;5~";
}
if (keypress.equals(kbCtrlF9)) {
// Control F9
return "\033[20;5~";
}
if (keypress.equals(kbCtrlF10)) {
// Control F10
return "\033[21;5~";
}
if (keypress.equals(kbCtrlF11)) {
// Control F11
return "\033[23;5~";
}
if (keypress.equals(kbCtrlF12)) {
// Control F12
return "\033[24;5~";
}
if (keypress.equalsWithoutModifiers(kbPgUp)) {
switch (type) {
case XTERM:
return xtermBuildKeySequence("\033[", '5', '~',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
default:
return "\033[5~";
}
}
if (keypress.equalsWithoutModifiers(kbPgDn)) {
switch (type) {
case XTERM:
return xtermBuildKeySequence("\033[", '6', '~',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
default:
return "\033[6~";
}
}
if (keypress.equalsWithoutModifiers(kbIns)) {
switch (type) {
case XTERM:
return xtermBuildKeySequence("\033[", '2', '~',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
default:
return "\033[2~";
}
}
if (keypress.equalsWithoutModifiers(kbDel)) {
switch (type) {
case XTERM:
return xtermBuildKeySequence("\033[", '3', '~',
keypress.isCtrl(), keypress.isAlt(),
keypress.isShift());
default:
// Delete sends real delete for VTxxx
return "\177";
}
}
if (keypress.equals(kbEnter)) {
return "\015";
}
if (keypress.equals(kbEsc)) {
return "\033";
}
if (keypress.equals(kbAltEsc)) {
return "\033\033";
}
if (keypress.equals(kbTab)) {
return "\011";
}
if ((keypress.equalsWithoutModifiers(kbBackTab)) ||
(keypress.equals(kbShiftTab))
) {
switch (type) {
case XTERM:
return "\033[Z";
default:
return "\011";
}
}
// Non-alt, non-ctrl characters
if (!keypress.isFnKey()) {
StringBuilder sb = new StringBuilder();
sb.append(keypress.getChar());
return sb.toString();
}
return "";
}
/**
* Map a symbol in any one of the VT100/VT220 character sets to a Unicode
* symbol.
*
* @param ch 8-bit character from the remote side
* @param charsetGl character set defined for GL
* @param charsetGr character set defined for GR
* @return character to display on the screen
*/
private char mapCharacterCharset(final char ch,
final CharacterSet charsetGl,
final CharacterSet charsetGr) {
int lookupChar = ch;
CharacterSet lookupCharset = charsetGl;
if (ch >= 0x80) {
assert ((type == DeviceType.VT220) || (type == DeviceType.XTERM));
lookupCharset = charsetGr;
lookupChar &= 0x7F;
}
switch (lookupCharset) {
case DRAWING:
return DECCharacterSets.SPECIAL_GRAPHICS[lookupChar];
case UK:
return DECCharacterSets.UK[lookupChar];
case US:
return DECCharacterSets.US_ASCII[lookupChar];
case NRC_DUTCH:
return DECCharacterSets.NL[lookupChar];
case NRC_FINNISH:
return DECCharacterSets.FI[lookupChar];
case NRC_FRENCH:
return DECCharacterSets.FR[lookupChar];
case NRC_FRENCH_CA:
return DECCharacterSets.FR_CA[lookupChar];
case NRC_GERMAN:
return DECCharacterSets.DE[lookupChar];
case NRC_ITALIAN:
return DECCharacterSets.IT[lookupChar];
case NRC_NORWEGIAN:
return DECCharacterSets.NO[lookupChar];
case NRC_SPANISH:
return DECCharacterSets.ES[lookupChar];
case NRC_SWEDISH:
return DECCharacterSets.SV[lookupChar];
case NRC_SWISS:
return DECCharacterSets.SWISS[lookupChar];
case DEC_SUPPLEMENTAL:
return DECCharacterSets.DEC_SUPPLEMENTAL[lookupChar];
case VT52_GRAPHICS:
return DECCharacterSets.VT52_SPECIAL_GRAPHICS[lookupChar];
case ROM:
return DECCharacterSets.US_ASCII[lookupChar];
case ROM_SPECIAL:
return DECCharacterSets.US_ASCII[lookupChar];
default:
throw new IllegalArgumentException("Invalid character set value: "
+ lookupCharset);
}
}
/**
* Map an 8-bit byte into a printable character.
*
* @param ch either 8-bit or Unicode character from the remote side
* @return character to display on the screen
*/
private char mapCharacter(final char ch) {
if (ch >= 0x100) {
// Unicode character, just return it
return ch;
}
CharacterSet charsetGl = currentState.g0Charset;
CharacterSet charsetGr = currentState.grCharset;
if (vt52Mode == true) {
if (shiftOut == true) {
// Shifted out character, pull from VT52 graphics
charsetGl = currentState.g1Charset;
charsetGr = CharacterSet.US;
} else {
// Normal
charsetGl = currentState.g0Charset;
charsetGr = CharacterSet.US;
}
// Pull the character
return mapCharacterCharset(ch, charsetGl, charsetGr);
}
// shiftOout
if (shiftOut == true) {
// Shifted out character, pull from G1
charsetGl = currentState.g1Charset;
charsetGr = currentState.grCharset;
// Pull the character
return mapCharacterCharset(ch, charsetGl, charsetGr);
}
// SS2
if (singleshift == Singleshift.SS2) {
singleshift = Singleshift.NONE;
// Shifted out character, pull from G2
charsetGl = currentState.g2Charset;
charsetGr = currentState.grCharset;
}
// SS3
if (singleshift == Singleshift.SS3) {
singleshift = Singleshift.NONE;
// Shifted out character, pull from G3
charsetGl = currentState.g3Charset;
charsetGr = currentState.grCharset;
}
if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
// Check for locking shift
switch (currentState.glLockshift) {
case G1_GR:
assert (false);
case G2_GR:
assert (false);
case G3_GR:
assert (false);
case G2_GL:
// LS2
charsetGl = currentState.g2Charset;
break;
case G3_GL:
// LS3
charsetGl = currentState.g3Charset;
break;
case NONE:
// Normal
charsetGl = currentState.g0Charset;
break;
}
switch (currentState.grLockshift) {
case G2_GL:
assert (false);
case G3_GL:
assert (false);
case G1_GR:
// LS1R
charsetGr = currentState.g1Charset;
break;
case G2_GR:
// LS2R
charsetGr = currentState.g2Charset;
break;
case G3_GR:
// LS3R
charsetGr = currentState.g3Charset;
break;
case NONE:
// Normal
charsetGr = CharacterSet.DEC_SUPPLEMENTAL;
break;
}
}
// Pull the character
return mapCharacterCharset(ch, charsetGl, charsetGr);
}
/**
* Scroll the text within a scrolling region up n lines.
*
* @param regionTop top row of the scrolling region
* @param regionBottom bottom row of the scrolling region
* @param n number of lines to scroll
*/
private void scrollingRegionScrollUp(final int regionTop,
final int regionBottom, final int n) {
if (regionTop >= regionBottom) {
return;
}
// Sanity check: see if there will be any characters left after the
// scroll
if (regionBottom + 1 - regionTop <= n) {
// There won't be anything left in the region, so just call
// eraseScreen() and return.
eraseScreen(regionTop, 0, regionBottom, width - 1, false);
return;
}
int remaining = regionBottom + 1 - regionTop - n;
List displayTop = display.subList(0, regionTop);
List displayBottom = display.subList(regionBottom + 1,
display.size());
List displayMiddle = display.subList(regionBottom + 1
- remaining, regionBottom + 1);
display = new LinkedList(displayTop);
display.addAll(displayMiddle);
for (int i = 0; i < n; i++) {
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
display.add(line);
}
display.addAll(displayBottom);
assert (display.size() == height);
}
/**
* Scroll the text within a scrolling region down n lines.
*
* @param regionTop top row of the scrolling region
* @param regionBottom bottom row of the scrolling region
* @param n number of lines to scroll
*/
private void scrollingRegionScrollDown(final int regionTop,
final int regionBottom, final int n) {
if (regionTop >= regionBottom) {
return;
}
// Sanity check: see if there will be any characters left after the
// scroll
if (regionBottom + 1 - regionTop <= n) {
// There won't be anything left in the region, so just call
// eraseScreen() and return.
eraseScreen(regionTop, 0, regionBottom, width - 1, false);
return;
}
int remaining = regionBottom + 1 - regionTop - n;
List displayTop = display.subList(0, regionTop);
List displayBottom = display.subList(regionBottom + 1,
display.size());
List displayMiddle = display.subList(regionTop,
regionTop + remaining);
display = new LinkedList(displayTop);
for (int i = 0; i < n; i++) {
DisplayLine line = new DisplayLine(currentState.attr);
line.setReverseColor(reverseVideo);
display.add(line);
}
display.addAll(displayMiddle);
display.addAll(displayBottom);
assert (display.size() == height);
}
/**
* Process a control character.
*
* @param ch 8-bit character from the remote side
*/
private void handleControlChar(final char ch) {
assert ((ch <= 0x1F) || ((ch >= 0x7F) && (ch <= 0x9F)));
switch (ch) {
case 0x00:
// NUL - discard
return;
case 0x05:
// ENQ
// Transmit the answerback message.
// Not supported
break;
case 0x07:
// BEL
// Not supported
break;
case 0x08:
// BS
cursorLeft(1, false);
break;
case 0x09:
// HT
advanceToNextTabStop();
break;
case 0x0A:
// LF
linefeed();
break;
case 0x0B:
// VT
linefeed();
break;
case 0x0C:
// FF
linefeed();
break;
case 0x0D:
// CR
carriageReturn();
break;
case 0x0E:
// SO
shiftOut = true;
currentState.glLockshift = LockshiftMode.NONE;
break;
case 0x0F:
// SI
shiftOut = false;
currentState.glLockshift = LockshiftMode.NONE;
break;
case 0x84:
// IND
ind();
break;
case 0x85:
// NEL
nel();
break;
case 0x88:
// HTS
hts();
break;
case 0x8D:
// RI
ri();
break;
case 0x8E:
// SS2
singleshift = Singleshift.SS2;
break;
case 0x8F:
// SS3
singleshift = Singleshift.SS3;
break;
default:
break;
}
}
/**
* Advance the cursor to the next tab stop.
*/
private void advanceToNextTabStop() {
if (tabStops.size() == 0) {
// Go to the rightmost column
cursorRight(rightMargin - currentState.cursorX, false);
return;
}
for (Integer stop: tabStops) {
if (stop > currentState.cursorX) {
cursorRight(stop - currentState.cursorX, false);
return;
}
}
/*
* We got here, meaning there isn't a tab stop beyond the current
* cursor position. Place the cursor of the right-most edge of the
* screen.
*/
cursorRight(rightMargin - currentState.cursorX, false);
}
/**
* Save a character into the collect buffer.
*
* @param ch character to save
*/
private void collect(final char ch) {
collectBuffer.append(ch);
}
/**
* Save a byte into the CSI parameters buffer.
*
* @param ch byte to save
*/
private void param(final byte ch) {
if (csiParams.size() == 0) {
csiParams.add(new Integer(0));
}
Integer x = csiParams.get(csiParams.size() - 1);
if ((ch >= '0') && (ch <= '9')) {
x *= 10;
x += (ch - '0');
csiParams.set(csiParams.size() - 1, x);
}
if (ch == ';') {
csiParams.add(new Integer(0));
}
}
/**
* Get a CSI parameter value, with a default.
*
* @param position parameter index. 0 is the first parameter.
* @param defaultValue value to use if csiParams[position] doesn't exist
* @return parameter value
*/
private int getCsiParam(final int position, final int defaultValue) {
if (csiParams.size() < position + 1) {
return defaultValue;
}
return csiParams.get(position).intValue();
}
/**
* Get a CSI parameter value, clamped to within min/max.
*
* @param position parameter index. 0 is the first parameter.
* @param defaultValue value to use if csiParams[position] doesn't exist
* @param minValue minimum value inclusive
* @param maxValue maximum value inclusive
* @return parameter value
*/
private int getCsiParam(final int position, final int defaultValue,
final int minValue, final int maxValue) {
assert (minValue <= maxValue);
int value = getCsiParam(position, defaultValue);
if (value < minValue) {
value = minValue;
}
if (value > maxValue) {
value = maxValue;
}
return value;
}
/**
* Set or unset a toggle.
*
* @param value true for set ('h'), false for reset ('l')
*/
private void setToggle(final boolean value) {
boolean decPrivateModeFlag = false;
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
}
}
for (Integer i: csiParams) {
switch (i) {
case 1:
if (decPrivateModeFlag == true) {
// DECCKM
if (value == true) {
// Use application arrow keys
arrowKeyMode = ArrowKeyMode.VT100;
} else {
// Use ANSI arrow keys
arrowKeyMode = ArrowKeyMode.ANSI;
}
}
break;
case 2:
if (decPrivateModeFlag == true) {
if (value == false) {
// DECANM
vt52Mode = true;
arrowKeyMode = ArrowKeyMode.VT52;
/*
* From the VT102 docs: "You use ANSI mode to select
* most terminal features; the terminal uses the same
* features when it switches to VT52 mode. You
* cannot, however, change most of these features in
* VT52 mode."
*
* In other words, do not reset any other attributes
* when switching between VT52 submode and ANSI.
*
* HOWEVER, the real vt100 does switch the character
* set according to Usenet.
*/
currentState.g0Charset = CharacterSet.US;
currentState.g1Charset = CharacterSet.DRAWING;
shiftOut = false;
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// VT52 mode is explicitly 7-bit
s8c1t = false;
singleshift = Singleshift.NONE;
}
}
} else {
// KAM
if (value == true) {
// Turn off keyboard
// Not supported
} else {
// Turn on keyboard
// Not supported
}
}
break;
case 3:
if (decPrivateModeFlag == true) {
// DECCOLM
if (value == true) {
// 132 columns
columns132 = true;
rightMargin = 131;
} else {
// 80 columns
columns132 = false;
if ((displayListener != null)
&& (type == DeviceType.XTERM)
) {
// For xterms, reset to the actual width, not 80
// columns.
width = displayListener.getDisplayWidth();
rightMargin = width - 1;
} else {
rightMargin = 79;
width = rightMargin + 1;
}
}
// Entire screen is cleared, and scrolling region is
// reset
eraseScreen(0, 0, height - 1, width - 1, false);
scrollRegionTop = 0;
scrollRegionBottom = height - 1;
// Also home the cursor
cursorPosition(0, 0);
}
break;
case 4:
if (decPrivateModeFlag == true) {
// DECSCLM
if (value == true) {
// Smooth scroll
// Not supported
} else {
// Jump scroll
// Not supported
}
} else {
// IRM
if (value == true) {
insertMode = true;
} else {
insertMode = false;
}
}
break;
case 5:
if (decPrivateModeFlag == true) {
// DECSCNM
if (value == true) {
/*
* Set selects reverse screen, a white screen
* background with black characters.
*/
if (reverseVideo != true) {
/*
* If in normal video, switch it back
*/
invertDisplayColors();
}
reverseVideo = true;
} else {
/*
* Reset selects normal screen, a black screen
* background with white characters.
*/
if (reverseVideo == true) {
/*
* If in reverse video already, switch it back
*/
invertDisplayColors();
}
reverseVideo = false;
}
}
break;
case 6:
if (decPrivateModeFlag == true) {
// DECOM
if (value == true) {
// Origin is relative to scroll region cursor.
// Cursor can NEVER leave scrolling region.
currentState.originMode = true;
cursorPosition(0, 0);
} else {
// Origin is absolute to entire screen. Cursor can
// leave the scrolling region via cup() and hvp().
currentState.originMode = false;
cursorPosition(0, 0);
}
}
break;
case 7:
if (decPrivateModeFlag == true) {
// DECAWM
if (value == true) {
// Turn linewrap on
currentState.lineWrap = true;
} else {
// Turn linewrap off
currentState.lineWrap = false;
}
}
break;
case 8:
if (decPrivateModeFlag == true) {
// DECARM
if (value == true) {
// Keyboard auto-repeat on
// Not supported
} else {
// Keyboard auto-repeat off
// Not supported
}
}
break;
case 12:
if (decPrivateModeFlag == false) {
// SRM
if (value == true) {
// Local echo off
fullDuplex = true;
} else {
// Local echo on
fullDuplex = false;
}
}
break;
case 18:
if (decPrivateModeFlag == true) {
// DECPFF
// Not supported
}
break;
case 19:
if (decPrivateModeFlag == true) {
// DECPEX
// Not supported
}
break;
case 20:
if (decPrivateModeFlag == false) {
// LNM
if (value == true) {
/*
* Set causes a received linefeed, form feed, or
* vertical tab to move cursor to first column of
* next line. RETURN transmits both a carriage return
* and linefeed. This selection is also called new
* line option.
*/
newLineMode = true;
} else {
/*
* Reset causes a received linefeed, form feed, or
* vertical tab to move cursor to next line in
* current column. RETURN transmits a carriage
* return.
*/
newLineMode = false;
}
}
break;
case 25:
if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
if (decPrivateModeFlag == true) {
// DECTCEM
if (value == true) {
// Visible cursor
cursorVisible = true;
} else {
// Invisible cursor
cursorVisible = false;
}
}
}
break;
case 42:
if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
if (decPrivateModeFlag == true) {
// DECNRCM
if (value == true) {
// Select national mode NRC
// Not supported
} else {
// Select multi-national mode
// Not supported
}
}
}
break;
case 1000:
if ((type == DeviceType.XTERM)
&& (decPrivateModeFlag == true)
) {
// Mouse: normal tracking mode
if (value == true) {
mouseProtocol = MouseProtocol.NORMAL;
} else {
mouseProtocol = MouseProtocol.OFF;
}
}
break;
case 1002:
if ((type == DeviceType.XTERM)
&& (decPrivateModeFlag == true)
) {
// Mouse: normal tracking mode
if (value == true) {
mouseProtocol = MouseProtocol.BUTTONEVENT;
} else {
mouseProtocol = MouseProtocol.OFF;
}
}
break;
case 1003:
if ((type == DeviceType.XTERM)
&& (decPrivateModeFlag == true)
) {
// Mouse: Any-event tracking mode
if (value == true) {
mouseProtocol = MouseProtocol.ANYEVENT;
} else {
mouseProtocol = MouseProtocol.OFF;
}
}
break;
case 1005:
if ((type == DeviceType.XTERM)
&& (decPrivateModeFlag == true)
) {
// Mouse: UTF-8 coordinates
if (value == true) {
mouseEncoding = MouseEncoding.UTF8;
} else {
mouseEncoding = MouseEncoding.X10;
}
}
break;
case 1006:
if ((type == DeviceType.XTERM)
&& (decPrivateModeFlag == true)
) {
// Mouse: SGR coordinates
if (value == true) {
mouseEncoding = MouseEncoding.SGR;
} else {
mouseEncoding = MouseEncoding.X10;
}
}
break;
default:
break;
}
}
}
/**
* DECSC - Save cursor.
*/
private void decsc() {
savedState.setTo(currentState);
}
/**
* DECRC - Restore cursor.
*/
private void decrc() {
currentState.setTo(savedState);
}
/**
* IND - Index.
*/
private void ind() {
// Move the cursor and scroll if necessary. If at the bottom line
// already, a scroll up is supposed to be performed.
if (currentState.cursorY == scrollRegionBottom) {
scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
}
cursorDown(1, true);
}
/**
* RI - Reverse index.
*/
private void ri() {
// Move the cursor and scroll if necessary. If at the top line
// already, a scroll down is supposed to be performed.
if (currentState.cursorY == scrollRegionTop) {
scrollingRegionScrollDown(scrollRegionTop, scrollRegionBottom, 1);
}
cursorUp(1, true);
}
/**
* NEL - Next line.
*/
private void nel() {
// Move the cursor and scroll if necessary. If at the bottom line
// already, a scroll up is supposed to be performed.
if (currentState.cursorY == scrollRegionBottom) {
scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
}
cursorDown(1, true);
// Reset to the beginning of the next line
currentState.cursorX = 0;
}
/**
* DECKPAM - Keypad application mode.
*/
private void deckpam() {
keypadMode = KeypadMode.Application;
}
/**
* DECKPNM - Keypad numeric mode.
*/
private void deckpnm() {
keypadMode = KeypadMode.Numeric;
}
/**
* Move up n spaces.
*
* @param n number of spaces to move
* @param honorScrollRegion if true, then do nothing if the cursor is
* outside the scrolling region
*/
private void cursorUp(final int n, final boolean honorScrollRegion) {
int top;
/*
* Special case: if a user moves the cursor from the right margin, we
* have to reset the VT100 right margin flag.
*/
if (n > 0) {
wrapLineFlag = false;
}
for (int i = 0; i < n; i++) {
if (honorScrollRegion == true) {
// Honor the scrolling region
if ((currentState.cursorY < scrollRegionTop)
|| (currentState.cursorY > scrollRegionBottom)
) {
// Outside region, do nothing
return;
}
// Inside region, go up
top = scrollRegionTop;
} else {
// Non-scrolling case
top = 0;
}
if (currentState.cursorY > top) {
currentState.cursorY--;
}
}
}
/**
* Move down n spaces.
*
* @param n number of spaces to move
* @param honorScrollRegion if true, then do nothing if the cursor is
* outside the scrolling region
*/
private void cursorDown(final int n, final boolean honorScrollRegion) {
int bottom;
/*
* Special case: if a user moves the cursor from the right margin, we
* have to reset the VT100 right margin flag.
*/
if (n > 0) {
wrapLineFlag = false;
}
for (int i = 0; i < n; i++) {
if (honorScrollRegion == true) {
// Honor the scrolling region
if (currentState.cursorY > scrollRegionBottom) {
// Outside region, do nothing
return;
}
// Inside region, go down
bottom = scrollRegionBottom;
} else {
// Non-scrolling case
bottom = height - 1;
}
if (currentState.cursorY < bottom) {
currentState.cursorY++;
}
}
}
/**
* Move left n spaces.
*
* @param n number of spaces to move
* @param honorScrollRegion if true, then do nothing if the cursor is
* outside the scrolling region
*/
private void cursorLeft(final int n, final boolean honorScrollRegion) {
/*
* Special case: if a user moves the cursor from the right margin, we
* have to reset the VT100 right margin flag.
*/
if (n > 0) {
wrapLineFlag = false;
}
for (int i = 0; i < n; i++) {
if (honorScrollRegion == true) {
// Honor the scrolling region
if ((currentState.cursorY < scrollRegionTop)
|| (currentState.cursorY > scrollRegionBottom)
) {
// Outside region, do nothing
return;
}
}
if (currentState.cursorX > 0) {
currentState.cursorX--;
}
}
}
/**
* Move right n spaces.
*
* @param n number of spaces to move
* @param honorScrollRegion if true, then do nothing if the cursor is
* outside the scrolling region
*/
private void cursorRight(final int n, final boolean honorScrollRegion) {
int rightMargin = this.rightMargin;
/*
* Special case: if a user moves the cursor from the right margin, we
* have to reset the VT100 right margin flag.
*/
if (n > 0) {
wrapLineFlag = false;
}
if (display.get(currentState.cursorY).isDoubleWidth()) {
rightMargin = ((rightMargin + 1) / 2) - 1;
}
for (int i = 0; i < n; i++) {
if (honorScrollRegion == true) {
// Honor the scrolling region
if ((currentState.cursorY < scrollRegionTop)
|| (currentState.cursorY > scrollRegionBottom)
) {
// Outside region, do nothing
return;
}
}
if (currentState.cursorX < rightMargin) {
currentState.cursorX++;
}
}
}
/**
* Move cursor to (col, row) where (0, 0) is the top-left corner.
*
* @param row row to move to
* @param col column to move to
*/
private void cursorPosition(int row, final int col) {
int rightMargin = this.rightMargin;
assert (col >= 0);
assert (row >= 0);
if (display.get(currentState.cursorY).isDoubleWidth()) {
rightMargin = ((rightMargin + 1) / 2) - 1;
}
// Set column number
currentState.cursorX = col;
// Sanity check, bring column back to margin.
if (currentState.cursorX > rightMargin) {
currentState.cursorX = rightMargin;
}
// Set row number
if (currentState.originMode == true) {
row += scrollRegionTop;
}
if (currentState.cursorY < row) {
cursorDown(row - currentState.cursorY, false);
} else if (currentState.cursorY > row) {
cursorUp(currentState.cursorY - row, false);
}
wrapLineFlag = false;
}
/**
* HTS - Horizontal tabulation set.
*/
private void hts() {
for (Integer stop: tabStops) {
if (stop == currentState.cursorX) {
// Already have a tab stop here
return;
}
}
// Append a tab stop to the end of the array and resort them
tabStops.add(currentState.cursorX);
Collections.sort(tabStops);
}
/**
* DECSWL - Single-width line.
*/
private void decswl() {
display.get(currentState.cursorY).setDoubleWidth(false);
display.get(currentState.cursorY).setDoubleHeight(0);
}
/**
* DECDWL - Double-width line.
*/
private void decdwl() {
display.get(currentState.cursorY).setDoubleWidth(true);
display.get(currentState.cursorY).setDoubleHeight(0);
}
/**
* DECHDL - Double-height + double-width line.
*
* @param topHalf if true, this sets the row to be the top half row of a
* double-height row
*/
private void dechdl(final boolean topHalf) {
display.get(currentState.cursorY).setDoubleWidth(true);
if (topHalf == true) {
display.get(currentState.cursorY).setDoubleHeight(1);
} else {
display.get(currentState.cursorY).setDoubleHeight(2);
}
}
/**
* DECALN - Screen alignment display.
*/
private void decaln() {
Cell newCell = new Cell();
newCell.setChar('E');
for (DisplayLine line: display) {
for (int i = 0; i < line.length(); i++) {
line.replace(i, newCell);
}
}
}
/**
* DECSCL - Compatibility level.
*/
private void decscl() {
int i = getCsiParam(0, 0);
int j = getCsiParam(1, 0);
if (i == 61) {
// Reset fonts
currentState.g0Charset = CharacterSet.US;
currentState.g1Charset = CharacterSet.DRAWING;
s8c1t = false;
} else if (i == 62) {
if ((j == 0) || (j == 2)) {
s8c1t = true;
} else if (j == 1) {
s8c1t = false;
}
}
}
/**
* CUD - Cursor down.
*/
private void cud() {
cursorDown(getCsiParam(0, 1, 1, height), true);
}
/**
* CUF - Cursor forward.
*/
private void cuf() {
cursorRight(getCsiParam(0, 1, 1, rightMargin + 1), true);
}
/**
* CUB - Cursor backward.
*/
private void cub() {
cursorLeft(getCsiParam(0, 1, 1, currentState.cursorX + 1), true);
}
/**
* CUU - Cursor up.
*/
private void cuu() {
cursorUp(getCsiParam(0, 1, 1, currentState.cursorY + 1), true);
}
/**
* CUP - Cursor position.
*/
private void cup() {
cursorPosition(getCsiParam(0, 1, 1, height) - 1,
getCsiParam(1, 1, 1, rightMargin + 1) - 1);
}
/**
* CNL - Cursor down and to column 1.
*/
private void cnl() {
cursorDown(getCsiParam(0, 1, 1, height), true);
// To column 0
cursorLeft(currentState.cursorX, true);
}
/**
* CPL - Cursor up and to column 1.
*/
private void cpl() {
cursorUp(getCsiParam(0, 1, 1, currentState.cursorY + 1), true);
// To column 0
cursorLeft(currentState.cursorX, true);
}
/**
* CHA - Cursor to column # in current row.
*/
private void cha() {
cursorPosition(currentState.cursorY,
getCsiParam(0, 1, 1, rightMargin + 1) - 1);
}
/**
* VPA - Cursor to row #, same column.
*/
private void vpa() {
cursorPosition(getCsiParam(0, 1, 1, height) - 1,
currentState.cursorX);
}
/**
* ED - Erase in display.
*/
private void ed() {
boolean honorProtected = false;
boolean decPrivateModeFlag = false;
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
}
}
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (decPrivateModeFlag == true)
) {
honorProtected = true;
}
int i = getCsiParam(0, 0);
if (i == 0) {
// Erase from here to end of screen
if (currentState.cursorY < height - 1) {
eraseScreen(currentState.cursorY + 1, 0, height - 1, width - 1,
honorProtected);
}
eraseLine(currentState.cursorX, width - 1, honorProtected);
} else if (i == 1) {
// Erase from beginning of screen to here
eraseScreen(0, 0, currentState.cursorY - 1, width - 1,
honorProtected);
eraseLine(0, currentState.cursorX, honorProtected);
} else if (i == 2) {
// Erase entire screen
eraseScreen(0, 0, height - 1, width - 1, honorProtected);
}
}
/**
* EL - Erase in line.
*/
private void el() {
boolean honorProtected = false;
boolean decPrivateModeFlag = false;
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
}
}
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (decPrivateModeFlag == true)
) {
honorProtected = true;
}
int i = getCsiParam(0, 0);
if (i == 0) {
// Erase from here to end of line
eraseLine(currentState.cursorX, width - 1, honorProtected);
} else if (i == 1) {
// Erase from beginning of line to here
eraseLine(0, currentState.cursorX, honorProtected);
} else if (i == 2) {
// Erase entire line
eraseLine(0, width - 1, honorProtected);
}
}
/**
* ECH - Erase # of characters in current row.
*/
private void ech() {
int i = getCsiParam(0, 1, 1, width);
// Erase from here to i characters
eraseLine(currentState.cursorX, currentState.cursorX + i - 1, false);
}
/**
* IL - Insert line.
*/
private void il() {
int i = getCsiParam(0, 1);
if ((currentState.cursorY >= scrollRegionTop)
&& (currentState.cursorY <= scrollRegionBottom)
) {
// I can get the same effect with a scroll-down
scrollingRegionScrollDown(currentState.cursorY,
scrollRegionBottom, i);
}
}
/**
* DCH - Delete char.
*/
private void dch() {
int n = getCsiParam(0, 1);
DisplayLine line = display.get(currentState.cursorY);
Cell blank = new Cell();
for (int i = 0; i < n; i++) {
line.delete(currentState.cursorX, blank);
}
}
/**
* ICH - Insert blank char at cursor.
*/
private void ich() {
int n = getCsiParam(0, 1);
DisplayLine line = display.get(currentState.cursorY);
Cell blank = new Cell();
for (int i = 0; i < n; i++) {
line.insert(currentState.cursorX, blank);
}
}
/**
* DL - Delete line.
*/
private void dl() {
int i = getCsiParam(0, 1);
if ((currentState.cursorY >= scrollRegionTop)
&& (currentState.cursorY <= scrollRegionBottom)) {
// I can get the same effect with a scroll-down
scrollingRegionScrollUp(currentState.cursorY,
scrollRegionBottom, i);
}
}
/**
* HVP - Horizontal and vertical position.
*/
private void hvp() {
cup();
}
/**
* REP - Repeat character.
*/
private void rep() {
int n = getCsiParam(0, 1);
for (int i = 0; i < n; i++) {
printCharacter(repCh);
}
}
/**
* SU - Scroll up.
*/
private void su() {
scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom,
getCsiParam(0, 1, 1, height));
}
/**
* SD - Scroll down.
*/
private void sd() {
scrollingRegionScrollDown(scrollRegionTop, scrollRegionBottom,
getCsiParam(0, 1, 1, height));
}
/**
* CBT - Go back X tab stops.
*/
private void cbt() {
int tabsToMove = getCsiParam(0, 1);
int tabI;
for (int i = 0; i < tabsToMove; i++) {
int j = currentState.cursorX;
for (tabI = 0; tabI < tabStops.size(); tabI++) {
if (tabStops.get(tabI) >= currentState.cursorX) {
break;
}
}
tabI--;
if (tabI <= 0) {
j = 0;
} else {
j = tabStops.get(tabI);
}
cursorPosition(currentState.cursorY, j);
}
}
/**
* CHT - Advance X tab stops.
*/
private void cht() {
int n = getCsiParam(0, 1);
for (int i = 0; i < n; i++) {
advanceToNextTabStop();
}
}
/**
* SGR - Select graphics rendition.
*/
private void sgr() {
if (csiParams.size() == 0) {
currentState.attr.reset();
return;
}
for (Integer i: csiParams) {
switch (i) {
case 0:
// Normal
currentState.attr.reset();
break;
case 1:
// Bold
currentState.attr.setBold(true);
break;
case 4:
// Underline
currentState.attr.setUnderline(true);
break;
case 5:
// Blink
currentState.attr.setBlink(true);
break;
case 7:
// Reverse
currentState.attr.setReverse(true);
break;
default:
break;
}
if (type == DeviceType.XTERM) {
switch (i) {
case 8:
// Invisible
// TODO
break;
default:
break;
}
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
switch (i) {
case 22:
// Normal intensity
currentState.attr.setBold(false);
break;
case 24:
// No underline
currentState.attr.setUnderline(false);
break;
case 25:
// No blink
currentState.attr.setBlink(false);
break;
case 27:
// Un-reverse
currentState.attr.setReverse(false);
break;
default:
break;
}
}
// A true VT100/102/220 does not support color, however everyone
// is used to their terminal emulator supporting color so we will
// unconditionally support color for all DeviceType's.
switch (i) {
case 30:
// Set black foreground
currentState.attr.setForeColor(Color.BLACK);
break;
case 31:
// Set red foreground
currentState.attr.setForeColor(Color.RED);
break;
case 32:
// Set green foreground
currentState.attr.setForeColor(Color.GREEN);
break;
case 33:
// Set yellow foreground
currentState.attr.setForeColor(Color.YELLOW);
break;
case 34:
// Set blue foreground
currentState.attr.setForeColor(Color.BLUE);
break;
case 35:
// Set magenta foreground
currentState.attr.setForeColor(Color.MAGENTA);
break;
case 36:
// Set cyan foreground
currentState.attr.setForeColor(Color.CYAN);
break;
case 37:
// Set white foreground
currentState.attr.setForeColor(Color.WHITE);
break;
case 38:
if (type == DeviceType.XTERM) {
/*
* Xterm supports T.416 / ISO-8613-3 codes to select
* either an indexed color or an RGB value. (It also
* permits these ISO-8613-3 SGR sequences to be separated
* by colons rather than semicolons.)
*
* We will not support any of these additional color
* codes at this time:
*
* 1. http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
* has a detailed discussion of the current state of
* RGB in various terminals, the point of which is
* that none of them really do the same thing despite
* all appearing to be "xterm".
*
* 2. As seen in
* https://bugs.kde.org/show_bug.cgi?id=107487#c3,
* even supporting just the "indexed mode" of these
* sequences (which could align easily with existing
* SGR colors) is assumed to mean full support of
* 24-bit RGB. So it is all or nothing.
*
* Finally, these sequences break the assumptions of
* standard ECMA-48 style parsers as pointed out at
* https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
* Therefore in order to keep a clean display, we cannot
* parse anything else in this sequence.
*/
return;
} else {
// Underscore on, default foreground color
currentState.attr.setUnderline(true);
currentState.attr.setForeColor(Color.WHITE);
}
break;
case 39:
// Underscore off, default foreground color
currentState.attr.setUnderline(false);
currentState.attr.setForeColor(Color.WHITE);
break;
case 40:
// Set black background
currentState.attr.setBackColor(Color.BLACK);
break;
case 41:
// Set red background
currentState.attr.setBackColor(Color.RED);
break;
case 42:
// Set green background
currentState.attr.setBackColor(Color.GREEN);
break;
case 43:
// Set yellow background
currentState.attr.setBackColor(Color.YELLOW);
break;
case 44:
// Set blue background
currentState.attr.setBackColor(Color.BLUE);
break;
case 45:
// Set magenta background
currentState.attr.setBackColor(Color.MAGENTA);
break;
case 46:
// Set cyan background
currentState.attr.setBackColor(Color.CYAN);
break;
case 47:
// Set white background
currentState.attr.setBackColor(Color.WHITE);
break;
case 48:
if (type == DeviceType.XTERM) {
/*
* Xterm supports T.416 / ISO-8613-3 codes to select
* either an indexed color or an RGB value. (It also
* permits these ISO-8613-3 SGR sequences to be separated
* by colons rather than semicolons.)
*
* We will not support this at this time. Also, in order
* to keep a clean display, we cannot parse anything else
* in this sequence.
*/
return;
}
break;
case 49:
// Default background
currentState.attr.setBackColor(Color.BLACK);
break;
default:
break;
}
}
}
/**
* DA - Device attributes.
*/
private void da() {
int extendedFlag = 0;
int i = 0;
if (collectBuffer.length() > 0) {
String args = collectBuffer.substring(1);
if (collectBuffer.charAt(0) == '>') {
extendedFlag = 1;
if (collectBuffer.length() >= 2) {
i = Integer.parseInt(args.toString());
}
} else if (collectBuffer.charAt(0) == '=') {
extendedFlag = 2;
if (collectBuffer.length() >= 2) {
i = Integer.parseInt(args.toString());
}
} else {
// Unknown code, bail out
return;
}
}
if ((i != 0) && (i != 1)) {
return;
}
if ((extendedFlag == 0) && (i == 0)) {
// Send string directly to remote side
writeRemote(deviceTypeResponse());
return;
}
if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
if ((extendedFlag == 1) && (i == 0)) {
/*
* Request "What type of terminal are you, what is your
* firmware version, and what hardware options do you have
* installed?"
*
* Respond: "I am a VT220 (identification code of 1), my
* firmware version is _____ (Pv), and I have _____ Po
* options installed."
*
* (Same as xterm)
*
*/
if (s8c1t == true) {
writeRemote("\u009b>1;10;0c");
} else {
writeRemote("\033[>1;10;0c");
}
}
}
// VT420 and up
if ((extendedFlag == 2) && (i == 0)) {
/*
* Request "What is your unit ID?"
*
* Respond: "I was manufactured at site 00 and have a unique ID
* number of 123."
*
*/
writeRemote("\033P!|00010203\033\\");
}
}
/**
* DECSTBM - Set top and bottom margins.
*/
private void decstbm() {
boolean decPrivateModeFlag = false;
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
}
}
if (decPrivateModeFlag) {
// This could be restore DEC private mode values.
// Ignore it.
} else {
// DECSTBM
int top = getCsiParam(0, 1, 1, height) - 1;
int bottom = getCsiParam(1, height, 1, height) - 1;
if (top > bottom) {
top = bottom;
}
scrollRegionTop = top;
scrollRegionBottom = bottom;
// Home cursor
cursorPosition(0, 0);
}
}
/**
* DECREQTPARM - Request terminal parameters.
*/
private void decreqtparm() {
int i = getCsiParam(0, 0);
if ((i != 0) && (i != 1)) {
return;
}
String str = "";
/*
* Request terminal parameters.
*
* Respond with:
*
* Parity NONE, 8 bits, xmitspeed 38400, recvspeed 38400.
* (CLoCk MULtiplier = 1, STP option flags = 0)
*
* (Same as xterm)
*/
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (s8c1t == true)
) {
str = String.format("\u009b%d;1;1;128;128;1;0x", i + 2);
} else {
str = String.format("\033[%d;1;1;128;128;1;0x", i + 2);
}
writeRemote(str);
}
/**
* DECSCA - Select Character Attributes.
*/
private void decsca() {
int i = getCsiParam(0, 0);
if ((i == 0) || (i == 2)) {
// Protect mode OFF
currentState.attr.setProtect(false);
}
if (i == 1) {
// Protect mode ON
currentState.attr.setProtect(true);
}
}
/**
* DECSTR - Soft Terminal Reset.
*/
private void decstr() {
// Do exactly like RIS - Reset to initial state
reset();
// Do I clear screen too? I think so...
eraseScreen(0, 0, height - 1, width - 1, false);
cursorPosition(0, 0);
}
/**
* DSR - Device status report.
*/
private void dsr() {
boolean decPrivateModeFlag = false;
int row = currentState.cursorY;
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
}
}
int i = getCsiParam(0, 0);
switch (i) {
case 5:
// Request status report. Respond with "OK, no malfunction."
// Send string directly to remote side
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (s8c1t == true)
) {
writeRemote("\u009b0n");
} else {
writeRemote("\033[0n");
}
break;
case 6:
// Request cursor position. Respond with current position.
if (currentState.originMode == true) {
row -= scrollRegionTop;
}
String str = "";
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (s8c1t == true)
) {
str = String.format("\u009b%d;%dR", row + 1,
currentState.cursorX + 1);
} else {
str = String.format("\033[%d;%dR", row + 1,
currentState.cursorX + 1);
}
// Send string directly to remote side
writeRemote(str);
break;
case 15:
if (decPrivateModeFlag == true) {
// Request printer status report. Respond with "Printer not
// connected."
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (s8c1t == true)) {
writeRemote("\u009b?13n");
} else {
writeRemote("\033[?13n");
}
}
break;
case 25:
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (decPrivateModeFlag == true)
) {
// Request user-defined keys are locked or unlocked. Respond
// with "User-defined keys are locked."
if (s8c1t == true) {
writeRemote("\u009b?21n");
} else {
writeRemote("\033[?21n");
}
}
break;
case 26:
if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
&& (decPrivateModeFlag == true)
) {
// Request keyboard language. Respond with "Keyboard
// language is North American."
if (s8c1t == true) {
writeRemote("\u009b?27;1n");
} else {
writeRemote("\033[?27;1n");
}
}
break;
default:
// Some other option, ignore
break;
}
}
/**
* TBC - Tabulation clear.
*/
private void tbc() {
int i = getCsiParam(0, 0);
if (i == 0) {
List newStops = new ArrayList();
for (Integer stop: tabStops) {
if (stop == currentState.cursorX) {
continue;
}
newStops.add(stop);
}
tabStops = newStops;
}
if (i == 3) {
tabStops.clear();
}
}
/**
* Erase the characters in the current line from the start column to the
* end column, inclusive.
*
* @param start starting column to erase (between 0 and width - 1)
* @param end ending column to erase (between 0 and width - 1)
* @param honorProtected if true, do not erase characters with the
* protected attribute set
*/
private void eraseLine(int start, int end, final boolean honorProtected) {
if (start > end) {
return;
}
if (end > width - 1) {
end = width - 1;
}
if (start < 0) {
start = 0;
}
for (int i = start; i <= end; i++) {
DisplayLine line = display.get(currentState.cursorY);
if ((!honorProtected)
|| ((honorProtected) && (!line.charAt(i).isProtect()))) {
switch (type) {
case VT100:
case VT102:
case VT220:
/*
* From the VT102 manual:
*
* Erasing a character also erases any character
* attribute of the character.
*/
line.setBlank(i);
break;
case XTERM:
/*
* Erase with the current color a.k.a. back-color erase
* (bce).
*/
line.setChar(i, ' ');
line.setAttr(i, currentState.attr);
break;
}
}
}
}
/**
* Erase a rectangular section of the screen, inclusive. end column,
* inclusive.
*
* @param startRow starting row to erase (between 0 and height - 1)
* @param startCol starting column to erase (between 0 and width - 1)
* @param endRow ending row to erase (between 0 and height - 1)
* @param endCol ending column to erase (between 0 and width - 1)
* @param honorProtected if true, do not erase characters with the
* protected attribute set
*/
private void eraseScreen(final int startRow, final int startCol,
final int endRow, final int endCol, final boolean honorProtected) {
int oldCursorY;
if ((startRow < 0)
|| (startCol < 0)
|| (endRow < 0)
|| (endCol < 0)
|| (endRow < startRow)
|| (endCol < startCol)
) {
return;
}
oldCursorY = currentState.cursorY;
for (int i = startRow; i <= endRow; i++) {
currentState.cursorY = i;
eraseLine(startCol, endCol, honorProtected);
// Erase display clears the double attributes
display.get(i).setDoubleWidth(false);
display.get(i).setDoubleHeight(0);
}
currentState.cursorY = oldCursorY;
}
/**
* VT220 printer functions. All of these are parsed, but won't do
* anything.
*/
private void printerFunctions() {
boolean decPrivateModeFlag = false;
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
}
}
int i = getCsiParam(0, 0);
switch (i) {
case 0:
if (decPrivateModeFlag == false) {
// Print screen
}
break;
case 1:
if (decPrivateModeFlag == true) {
// Print cursor line
}
break;
case 4:
if (decPrivateModeFlag == true) {
// Auto print mode OFF
} else {
// Printer controller OFF
// Characters re-appear on the screen
printerControllerMode = false;
}
break;
case 5:
if (decPrivateModeFlag == true) {
// Auto print mode
} else {
// Printer controller
// Characters get sucked into oblivion
printerControllerMode = true;
}
break;
default:
break;
}
}
/**
* Handle the SCAN_OSC_STRING state. Handle this in VT100 because lots
* of remote systems will send an XTerm title sequence even if TERM isn't
* xterm.
*
* @param xtermChar the character received from the remote side
*/
private void oscPut(final char xtermChar) {
// Collect first
collectBuffer.append(xtermChar);
// Xterm cases...
if (xtermChar == 0x07) {
String args = collectBuffer.substring(0,
collectBuffer.length() - 1);
String [] p = args.toString().split(";");
if (p.length > 0) {
if ((p[0].equals("0")) || (p[0].equals("2"))) {
if (p.length > 1) {
// Screen title
screenTitle = p[1];
}
}
}
// Go to SCAN_GROUND state
toGround();
return;
}
}
/**
* Run this input character through the ECMA48 state machine.
*
* @param ch character from the remote side
*/
private void consume(char ch) {
// DEBUG
// System.err.printf("%c", ch);
// Special case for VT10x: 7-bit characters only
if ((type == DeviceType.VT100) || (type == DeviceType.VT102)) {
ch = (char)(ch & 0x7F);
}
// Special "anywhere" states
// 18, 1A --> execute, then switch to SCAN_GROUND
if ((ch == 0x18) || (ch == 0x1A)) {
// CAN and SUB abort escape sequences
toGround();
return;
}
// 80-8F, 91-97, 99, 9A, 9C --> execute, then switch to SCAN_GROUND
// 0x1B == ESCAPE
if ((ch == 0x1B)
&& (scanState != ScanState.DCS_ENTRY)
&& (scanState != ScanState.DCS_INTERMEDIATE)
&& (scanState != ScanState.DCS_IGNORE)
&& (scanState != ScanState.DCS_PARAM)
&& (scanState != ScanState.DCS_PASSTHROUGH)
) {
scanState = ScanState.ESCAPE;
return;
}
// 0x9B == CSI 8-bit sequence
if (ch == 0x9B) {
scanState = ScanState.CSI_ENTRY;
return;
}
// 0x9D goes to ScanState.OSC_STRING
if (ch == 0x9D) {
scanState = ScanState.OSC_STRING;
return;
}
// 0x90 goes to DCS_ENTRY
if (ch == 0x90) {
scanState = ScanState.DCS_ENTRY;
return;
}
// 0x98, 0x9E, and 0x9F go to SOSPMAPC_STRING
if ((ch == 0x98) || (ch == 0x9E) || (ch == 0x9F)) {
scanState = ScanState.SOSPMAPC_STRING;
return;
}
// 0x7F (DEL) is always discarded
if (ch == 0x7F) {
return;
}
switch (scanState) {
case GROUND:
// 00-17, 19, 1C-1F --> execute
// 80-8F, 91-9A, 9C --> execute
if ((ch <= 0x1F) || ((ch >= 0x80) && (ch <= 0x9F))) {
handleControlChar(ch);
}
// 20-7F --> print
if (((ch >= 0x20) && (ch <= 0x7F))
|| (ch >= 0xA0)
) {
// VT220 printer --> trash bin
if (((type == DeviceType.VT220)
|| (type == DeviceType.XTERM))
&& (printerControllerMode == true)
) {
return;
}
// Hang onto this character
repCh = mapCharacter(ch);
// Print this character
printCharacter(repCh);
}
return;
case ESCAPE:
// 00-17, 19, 1C-1F --> execute
if (ch <= 0x1F) {
handleControlChar(ch);
return;
}
// 20-2F --> collect, then switch to ESCAPE_INTERMEDIATE
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
scanState = ScanState.ESCAPE_INTERMEDIATE;
return;
}
// 30-4F, 51-57, 59, 5A, 5C, 60-7E --> dispatch, then switch to GROUND
if ((ch >= 0x30) && (ch <= 0x4F)) {
switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
break;
case '7':
// DECSC - Save cursor
// Note this code overlaps both ANSI and VT52 mode
decsc();
break;
case '8':
// DECRC - Restore cursor
// Note this code overlaps both ANSI and VT52 mode
decrc();
break;
case '9':
case ':':
case ';':
break;
case '<':
if (vt52Mode == true) {
// DECANM - Enter ANSI mode
vt52Mode = false;
arrowKeyMode = ArrowKeyMode.VT100;
/*
* From the VT102 docs: "You use ANSI mode to select
* most terminal features; the terminal uses the same
* features when it switches to VT52 mode. You
* cannot, however, change most of these features in
* VT52 mode."
*
* In other words, do not reset any other attributes
* when switching between VT52 submode and ANSI.
*/
// Reset fonts
currentState.g0Charset = CharacterSet.US;
currentState.g1Charset = CharacterSet.DRAWING;
s8c1t = false;
singleshift = Singleshift.NONE;
currentState.glLockshift = LockshiftMode.NONE;
currentState.grLockshift = LockshiftMode.NONE;
}
break;
case '=':
// DECKPAM - Keypad application mode
// Note this code overlaps both ANSI and VT52 mode
deckpam();
break;
case '>':
// DECKPNM - Keypad numeric mode
// Note this code overlaps both ANSI and VT52 mode
deckpnm();
break;
case '?':
case '@':
break;
case 'A':
if (vt52Mode == true) {
// Cursor up, and stop at the top without scrolling
cursorUp(1, false);
}
break;
case 'B':
if (vt52Mode == true) {
// Cursor down, and stop at the bottom without scrolling
cursorDown(1, false);
}
break;
case 'C':
if (vt52Mode == true) {
// Cursor right, and stop at the right without scrolling
cursorRight(1, false);
}
break;
case 'D':
if (vt52Mode == true) {
// Cursor left, and stop at the left without scrolling
cursorLeft(1, false);
} else {
// IND - Index
ind();
}
break;
case 'E':
if (vt52Mode == true) {
// Nothing
} else {
// NEL - Next line
nel();
}
break;
case 'F':
if (vt52Mode == true) {
// G0 --> Special graphics
currentState.g0Charset = CharacterSet.VT52_GRAPHICS;
}
break;
case 'G':
if (vt52Mode == true) {
// G0 --> ASCII set
currentState.g0Charset = CharacterSet.US;
}
break;
case 'H':
if (vt52Mode == true) {
// Cursor to home
cursorPosition(0, 0);
} else {
// HTS - Horizontal tabulation set
hts();
}
break;
case 'I':
if (vt52Mode == true) {
// Reverse line feed. Same as RI.
ri();
}
break;
case 'J':
if (vt52Mode == true) {
// Erase to end of screen
eraseLine(currentState.cursorX, width - 1, false);
eraseScreen(currentState.cursorY + 1, 0, height - 1,
width - 1, false);
}
break;
case 'K':
if (vt52Mode == true) {
// Erase to end of line
eraseLine(currentState.cursorX, width - 1, false);
}
break;
case 'L':
break;
case 'M':
if (vt52Mode == true) {
// Nothing
} else {
// RI - Reverse index
ri();
}
break;
case 'N':
if (vt52Mode == false) {
// SS2
singleshift = Singleshift.SS2;
}
break;
case 'O':
if (vt52Mode == false) {
// SS3
singleshift = Singleshift.SS3;
}
break;
}
toGround();
return;
}
if ((ch >= 0x51) && (ch <= 0x57)) {
switch (ch) {
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
break;
}
toGround();
return;
}
if (ch == 0x59) {
// 'Y'
if (vt52Mode == true) {
scanState = ScanState.VT52_DIRECT_CURSOR_ADDRESS;
} else {
toGround();
}
return;
}
if (ch == 0x5A) {
// 'Z'
if (vt52Mode == true) {
// Identify
// Send string directly to remote side
writeRemote("\033/Z");
} else {
// DECID
// Send string directly to remote side
writeRemote(deviceTypeResponse());
}
toGround();
return;
}
if (ch == 0x5C) {
// '\'
toGround();
return;
}
// VT52 cannot get to any of these other states
if (vt52Mode == true) {
toGround();
return;
}
if ((ch >= 0x60) && (ch <= 0x7E)) {
switch (ch) {
case '`':
case 'a':
case 'b':
break;
case 'c':
// RIS - Reset to initial state
reset();
// Do I clear screen too? I think so...
eraseScreen(0, 0, height - 1, width - 1, false);
cursorPosition(0, 0);
break;
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
break;
case 'n':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// VT220 lockshift G2 into GL
currentState.glLockshift = LockshiftMode.G2_GL;
shiftOut = false;
}
break;
case 'o':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// VT220 lockshift G3 into GL
currentState.glLockshift = LockshiftMode.G3_GL;
shiftOut = false;
}
break;
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case '{':
break;
case '|':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// VT220 lockshift G3 into GR
currentState.grLockshift = LockshiftMode.G3_GR;
shiftOut = false;
}
break;
case '}':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// VT220 lockshift G2 into GR
currentState.grLockshift = LockshiftMode.G2_GR;
shiftOut = false;
}
break;
case '~':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// VT220 lockshift G1 into GR
currentState.grLockshift = LockshiftMode.G1_GR;
shiftOut = false;
}
break;
}
toGround();
}
// 7F --> ignore
// 0x5B goes to CSI_ENTRY
if (ch == 0x5B) {
scanState = ScanState.CSI_ENTRY;
}
// 0x5D goes to OSC_STRING
if (ch == 0x5D) {
scanState = ScanState.OSC_STRING;
}
// 0x50 goes to DCS_ENTRY
if (ch == 0x50) {
scanState = ScanState.DCS_ENTRY;
}
// 0x58, 0x5E, and 0x5F go to SOSPMAPC_STRING
if ((ch == 0x58) || (ch == 0x5E) || (ch == 0x5F)) {
scanState = ScanState.SOSPMAPC_STRING;
}
return;
case ESCAPE_INTERMEDIATE:
// 00-17, 19, 1C-1F --> execute
if (ch <= 0x1F) {
handleControlChar(ch);
}
// 20-2F --> collect
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
}
// 30-7E --> dispatch, then switch to GROUND
if ((ch >= 0x30) && (ch <= 0x7E)) {
switch (ch) {
case '0':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> Special graphics
currentState.g0Charset = CharacterSet.DRAWING;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> Special graphics
currentState.g1Charset = CharacterSet.DRAWING;
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> Special graphics
currentState.g2Charset = CharacterSet.DRAWING;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> Special graphics
currentState.g3Charset = CharacterSet.DRAWING;
}
}
break;
case '1':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> Alternate character ROM standard character set
currentState.g0Charset = CharacterSet.ROM;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> Alternate character ROM standard character set
currentState.g1Charset = CharacterSet.ROM;
}
break;
case '2':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> Alternate character ROM special graphics
currentState.g0Charset = CharacterSet.ROM_SPECIAL;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> Alternate character ROM special graphics
currentState.g1Charset = CharacterSet.ROM_SPECIAL;
}
break;
case '3':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '#')) {
// DECDHL - Double-height line (top half)
dechdl(true);
}
break;
case '4':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '#')) {
// DECDHL - Double-height line (bottom half)
dechdl(false);
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> DUTCH
currentState.g0Charset = CharacterSet.NRC_DUTCH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> DUTCH
currentState.g1Charset = CharacterSet.NRC_DUTCH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> DUTCH
currentState.g2Charset = CharacterSet.NRC_DUTCH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> DUTCH
currentState.g3Charset = CharacterSet.NRC_DUTCH;
}
}
break;
case '5':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '#')) {
// DECSWL - Single-width line
decswl();
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> FINNISH
currentState.g0Charset = CharacterSet.NRC_FINNISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> FINNISH
currentState.g1Charset = CharacterSet.NRC_FINNISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> FINNISH
currentState.g2Charset = CharacterSet.NRC_FINNISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> FINNISH
currentState.g3Charset = CharacterSet.NRC_FINNISH;
}
}
break;
case '6':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '#')) {
// DECDWL - Double-width line
decdwl();
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> NORWEGIAN
currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> NORWEGIAN
currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> NORWEGIAN
currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> NORWEGIAN
currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
}
}
break;
case '7':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> SWEDISH
currentState.g0Charset = CharacterSet.NRC_SWEDISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> SWEDISH
currentState.g1Charset = CharacterSet.NRC_SWEDISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> SWEDISH
currentState.g2Charset = CharacterSet.NRC_SWEDISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> SWEDISH
currentState.g3Charset = CharacterSet.NRC_SWEDISH;
}
}
break;
case '8':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '#')) {
// DECALN - Screen alignment display
decaln();
}
break;
case '9':
case ':':
case ';':
break;
case '<':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> DEC_SUPPLEMENTAL
currentState.g0Charset = CharacterSet.DEC_SUPPLEMENTAL;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> DEC_SUPPLEMENTAL
currentState.g1Charset = CharacterSet.DEC_SUPPLEMENTAL;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> DEC_SUPPLEMENTAL
currentState.g2Charset = CharacterSet.DEC_SUPPLEMENTAL;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> DEC_SUPPLEMENTAL
currentState.g3Charset = CharacterSet.DEC_SUPPLEMENTAL;
}
}
break;
case '=':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> SWISS
currentState.g0Charset = CharacterSet.NRC_SWISS;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> SWISS
currentState.g1Charset = CharacterSet.NRC_SWISS;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> SWISS
currentState.g2Charset = CharacterSet.NRC_SWISS;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> SWISS
currentState.g3Charset = CharacterSet.NRC_SWISS;
}
}
break;
case '>':
case '?':
case '@':
break;
case 'A':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> United Kingdom set
currentState.g0Charset = CharacterSet.UK;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> United Kingdom set
currentState.g1Charset = CharacterSet.UK;
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> United Kingdom set
currentState.g2Charset = CharacterSet.UK;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> United Kingdom set
currentState.g3Charset = CharacterSet.UK;
}
}
break;
case 'B':
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> ASCII set
currentState.g0Charset = CharacterSet.US;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> ASCII set
currentState.g1Charset = CharacterSet.US;
}
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> ASCII
currentState.g2Charset = CharacterSet.US;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> ASCII
currentState.g3Charset = CharacterSet.US;
}
}
break;
case 'C':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> FINNISH
currentState.g0Charset = CharacterSet.NRC_FINNISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> FINNISH
currentState.g1Charset = CharacterSet.NRC_FINNISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> FINNISH
currentState.g2Charset = CharacterSet.NRC_FINNISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> FINNISH
currentState.g3Charset = CharacterSet.NRC_FINNISH;
}
}
break;
case 'D':
break;
case 'E':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> NORWEGIAN
currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> NORWEGIAN
currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> NORWEGIAN
currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> NORWEGIAN
currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
}
}
break;
case 'F':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ' ')) {
// S7C1T
s8c1t = false;
}
}
break;
case 'G':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ' ')) {
// S8C1T
s8c1t = true;
}
}
break;
case 'H':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> SWEDISH
currentState.g0Charset = CharacterSet.NRC_SWEDISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> SWEDISH
currentState.g1Charset = CharacterSet.NRC_SWEDISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> SWEDISH
currentState.g2Charset = CharacterSet.NRC_SWEDISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> SWEDISH
currentState.g3Charset = CharacterSet.NRC_SWEDISH;
}
}
break;
case 'I':
case 'J':
break;
case 'K':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> GERMAN
currentState.g0Charset = CharacterSet.NRC_GERMAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> GERMAN
currentState.g1Charset = CharacterSet.NRC_GERMAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> GERMAN
currentState.g2Charset = CharacterSet.NRC_GERMAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> GERMAN
currentState.g3Charset = CharacterSet.NRC_GERMAN;
}
}
break;
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
break;
case 'Q':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> FRENCH_CA
currentState.g0Charset = CharacterSet.NRC_FRENCH_CA;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> FRENCH_CA
currentState.g1Charset = CharacterSet.NRC_FRENCH_CA;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> FRENCH_CA
currentState.g2Charset = CharacterSet.NRC_FRENCH_CA;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> FRENCH_CA
currentState.g3Charset = CharacterSet.NRC_FRENCH_CA;
}
}
break;
case 'R':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> FRENCH
currentState.g0Charset = CharacterSet.NRC_FRENCH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> FRENCH
currentState.g1Charset = CharacterSet.NRC_FRENCH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> FRENCH
currentState.g2Charset = CharacterSet.NRC_FRENCH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> FRENCH
currentState.g3Charset = CharacterSet.NRC_FRENCH;
}
}
break;
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
break;
case 'Y':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> ITALIAN
currentState.g0Charset = CharacterSet.NRC_ITALIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> ITALIAN
currentState.g1Charset = CharacterSet.NRC_ITALIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> ITALIAN
currentState.g2Charset = CharacterSet.NRC_ITALIAN;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> ITALIAN
currentState.g3Charset = CharacterSet.NRC_ITALIAN;
}
}
break;
case 'Z':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '(')) {
// G0 --> SPANISH
currentState.g0Charset = CharacterSet.NRC_SPANISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == ')')) {
// G1 --> SPANISH
currentState.g1Charset = CharacterSet.NRC_SPANISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '*')) {
// G2 --> SPANISH
currentState.g2Charset = CharacterSet.NRC_SPANISH;
}
if ((collectBuffer.length() == 1)
&& (collectBuffer.charAt(0) == '+')) {
// G3 --> SPANISH
currentState.g3Charset = CharacterSet.NRC_SPANISH;
}
}
break;
case '[':
case '\\':
case ']':
case '^':
case '_':
case '`':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case '{':
case '|':
case '}':
case '~':
break;
}
toGround();
}
// 7F --> ignore
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
return;
case CSI_ENTRY:
// 00-17, 19, 1C-1F --> execute
if (ch <= 0x1F) {
handleControlChar(ch);
}
// 20-2F --> collect, then switch to CSI_INTERMEDIATE
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
scanState = ScanState.CSI_INTERMEDIATE;
}
// 30-39, 3B --> param, then switch to CSI_PARAM
if ((ch >= '0') && (ch <= '9')) {
param((byte) ch);
scanState = ScanState.CSI_PARAM;
}
if (ch == ';') {
param((byte) ch);
scanState = ScanState.CSI_PARAM;
}
// 3C-3F --> collect, then switch to CSI_PARAM
if ((ch >= 0x3C) && (ch <= 0x3F)) {
collect(ch);
scanState = ScanState.CSI_PARAM;
}
// 40-7E --> dispatch, then switch to GROUND
if ((ch >= 0x40) && (ch <= 0x7E)) {
switch (ch) {
case '@':
// ICH - Insert character
ich();
break;
case 'A':
// CUU - Cursor up
cuu();
break;
case 'B':
// CUD - Cursor down
cud();
break;
case 'C':
// CUF - Cursor forward
cuf();
break;
case 'D':
// CUB - Cursor backward
cub();
break;
case 'E':
// CNL - Cursor down and to column 1
if (type == DeviceType.XTERM) {
cnl();
}
break;
case 'F':
// CPL - Cursor up and to column 1
if (type == DeviceType.XTERM) {
cpl();
}
break;
case 'G':
// CHA - Cursor to column # in current row
if (type == DeviceType.XTERM) {
cha();
}
break;
case 'H':
// CUP - Cursor position
cup();
break;
case 'I':
// CHT - Cursor forward X tab stops (default 1)
if (type == DeviceType.XTERM) {
cht();
}
break;
case 'J':
// ED - Erase in display
ed();
break;
case 'K':
// EL - Erase in line
el();
break;
case 'L':
// IL - Insert line
il();
break;
case 'M':
// DL - Delete line
dl();
break;
case 'N':
case 'O':
break;
case 'P':
// DCH - Delete character
dch();
break;
case 'Q':
case 'R':
break;
case 'S':
// Scroll up X lines (default 1)
if (type == DeviceType.XTERM) {
su();
}
break;
case 'T':
// Scroll down X lines (default 1)
if (type == DeviceType.XTERM) {
sd();
}
break;
case 'U':
case 'V':
case 'W':
break;
case 'X':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// ECH - Erase character
ech();
}
break;
case 'Y':
break;
case 'Z':
// CBT - Cursor backward X tab stops (default 1)
if (type == DeviceType.XTERM) {
cbt();
}
break;
case '[':
case '\\':
case ']':
case '^':
case '_':
break;
case '`':
// HPA - Cursor to column # in current row. Same as CHA
if (type == DeviceType.XTERM) {
cha();
}
break;
case 'a':
// HPR - Cursor right. Same as CUF
if (type == DeviceType.XTERM) {
cuf();
}
break;
case 'b':
// REP - Repeat last char X times
if (type == DeviceType.XTERM) {
rep();
}
break;
case 'c':
// DA - Device attributes
da();
break;
case 'd':
// VPA - Cursor to row, current column.
if (type == DeviceType.XTERM) {
vpa();
}
break;
case 'e':
// VPR - Cursor down. Same as CUD
if (type == DeviceType.XTERM) {
cud();
}
break;
case 'f':
// HVP - Horizontal and vertical position
hvp();
break;
case 'g':
// TBC - Tabulation clear
tbc();
break;
case 'h':
// Sets an ANSI or DEC private toggle
setToggle(true);
break;
case 'i':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// Printer functions
printerFunctions();
}
break;
case 'j':
case 'k':
break;
case 'l':
// Sets an ANSI or DEC private toggle
setToggle(false);
break;
case 'm':
// SGR - Select graphics rendition
sgr();
break;
case 'n':
// DSR - Device status report
dsr();
break;
case 'o':
case 'p':
break;
case 'q':
// DECLL - Load leds
// Not supported
break;
case 'r':
// DECSTBM - Set top and bottom margins
decstbm();
break;
case 's':
// Save cursor (ANSI.SYS)
if (type == DeviceType.XTERM) {
savedState.cursorX = currentState.cursorX;
savedState.cursorY = currentState.cursorY;
}
break;
case 't':
break;
case 'u':
// Restore cursor (ANSI.SYS)
if (type == DeviceType.XTERM) {
cursorPosition(savedState.cursorY, savedState.cursorX);
}
break;
case 'v':
case 'w':
break;
case 'x':
// DECREQTPARM - Request terminal parameters
decreqtparm();
break;
case 'y':
case 'z':
case '{':
case '|':
case '}':
case '~':
break;
}
toGround();
}
// 7F --> ignore
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
// 0x3A goes to CSI_IGNORE
if (ch == 0x3A) {
scanState = ScanState.CSI_IGNORE;
}
return;
case CSI_PARAM:
// 00-17, 19, 1C-1F --> execute
if (ch <= 0x1F) {
handleControlChar(ch);
}
// 20-2F --> collect, then switch to CSI_INTERMEDIATE
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
scanState = ScanState.CSI_INTERMEDIATE;
}
// 30-39, 3B --> param
if ((ch >= '0') && (ch <= '9')) {
param((byte) ch);
}
if (ch == ';') {
param((byte) ch);
}
// 0x3A goes to CSI_IGNORE
if (ch == 0x3A) {
scanState = ScanState.CSI_IGNORE;
}
// 0x3C-3F goes to CSI_IGNORE
if ((ch >= 0x3C) && (ch <= 0x3F)) {
scanState = ScanState.CSI_IGNORE;
}
// 40-7E --> dispatch, then switch to GROUND
if ((ch >= 0x40) && (ch <= 0x7E)) {
switch (ch) {
case '@':
// ICH - Insert character
ich();
break;
case 'A':
// CUU - Cursor up
cuu();
break;
case 'B':
// CUD - Cursor down
cud();
break;
case 'C':
// CUF - Cursor forward
cuf();
break;
case 'D':
// CUB - Cursor backward
cub();
break;
case 'E':
// CNL - Cursor down and to column 1
if (type == DeviceType.XTERM) {
cnl();
}
break;
case 'F':
// CPL - Cursor up and to column 1
if (type == DeviceType.XTERM) {
cpl();
}
break;
case 'G':
// CHA - Cursor to column # in current row
if (type == DeviceType.XTERM) {
cha();
}
break;
case 'H':
// CUP - Cursor position
cup();
break;
case 'I':
// CHT - Cursor forward X tab stops (default 1)
if (type == DeviceType.XTERM) {
cht();
}
break;
case 'J':
// ED - Erase in display
ed();
break;
case 'K':
// EL - Erase in line
el();
break;
case 'L':
// IL - Insert line
il();
break;
case 'M':
// DL - Delete line
dl();
break;
case 'N':
case 'O':
break;
case 'P':
// DCH - Delete character
dch();
break;
case 'Q':
case 'R':
break;
case 'S':
// Scroll up X lines (default 1)
if (type == DeviceType.XTERM) {
su();
}
break;
case 'T':
// Scroll down X lines (default 1)
if (type == DeviceType.XTERM) {
sd();
}
break;
case 'U':
case 'V':
case 'W':
break;
case 'X':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// ECH - Erase character
ech();
}
break;
case 'Y':
break;
case 'Z':
// CBT - Cursor backward X tab stops (default 1)
if (type == DeviceType.XTERM) {
cbt();
}
break;
case '[':
case '\\':
case ']':
case '^':
case '_':
break;
case '`':
// HPA - Cursor to column # in current row. Same as CHA
if (type == DeviceType.XTERM) {
cha();
}
break;
case 'a':
// HPR - Cursor right. Same as CUF
if (type == DeviceType.XTERM) {
cuf();
}
break;
case 'b':
// REP - Repeat last char X times
if (type == DeviceType.XTERM) {
rep();
}
break;
case 'c':
// DA - Device attributes
da();
break;
case 'd':
// VPA - Cursor to row, current column.
if (type == DeviceType.XTERM) {
vpa();
}
break;
case 'e':
// VPR - Cursor down. Same as CUD
if (type == DeviceType.XTERM) {
cud();
}
break;
case 'f':
// HVP - Horizontal and vertical position
hvp();
break;
case 'g':
// TBC - Tabulation clear
tbc();
break;
case 'h':
// Sets an ANSI or DEC private toggle
setToggle(true);
break;
case 'i':
if ((type == DeviceType.VT220)
|| (type == DeviceType.XTERM)) {
// Printer functions
printerFunctions();
}
break;
case 'j':
case 'k':
break;
case 'l':
// Sets an ANSI or DEC private toggle
setToggle(false);
break;
case 'm':
// SGR - Select graphics rendition
sgr();
break;
case 'n':
// DSR - Device status report
dsr();
break;
case 'o':
case 'p':
break;
case 'q':
// DECLL - Load leds
// Not supported
break;
case 'r':
// DECSTBM - Set top and bottom margins
decstbm();
break;
case 's':
case 't':
case 'u':
case 'v':
case 'w':
break;
case 'x':
// DECREQTPARM - Request terminal parameters
decreqtparm();
break;
case 'y':
case 'z':
case '{':
case '|':
case '}':
case '~':
break;
}
toGround();
}
// 7F --> ignore
return;
case CSI_INTERMEDIATE:
// 00-17, 19, 1C-1F --> execute
if (ch <= 0x1F) {
handleControlChar(ch);
}
// 20-2F --> collect
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
}
// 0x30-3F goes to CSI_IGNORE
if ((ch >= 0x30) && (ch <= 0x3F)) {
scanState = ScanState.CSI_IGNORE;
}
// 40-7E --> dispatch, then switch to GROUND
if ((ch >= 0x40) && (ch <= 0x7E)) {
switch (ch) {
case '@':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
case '[':
case '\\':
case ']':
case '^':
case '_':
case '`':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
break;
case 'p':
if (((type == DeviceType.VT220)
|| (type == DeviceType.XTERM))
&& (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
) {
// DECSCL - compatibility level
decscl();
}
if ((type == DeviceType.XTERM)
&& (collectBuffer.charAt(collectBuffer.length() - 1) == '!')
) {
// DECSTR - Soft terminal reset
decstr();
}
break;
case 'q':
if (((type == DeviceType.VT220)
|| (type == DeviceType.XTERM))
&& (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
) {
// DECSCA
decsca();
}
break;
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case '{':
case '|':
case '}':
case '~':
break;
}
toGround();
}
// 7F --> ignore
return;
case CSI_IGNORE:
// 00-17, 19, 1C-1F --> execute
if (ch <= 0x1F) {
handleControlChar(ch);
}
// 20-2F --> collect
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
}
// 40-7E --> ignore, then switch to GROUND
if ((ch >= 0x40) && (ch <= 0x7E)) {
toGround();
}
// 20-3F, 7F --> ignore
return;
case DCS_ENTRY:
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
// 0x1B 0x5C goes to GROUND
if (ch == 0x1B) {
collect(ch);
}
if (ch == 0x5C) {
if ((collectBuffer.length() > 0)
&& (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
) {
toGround();
}
}
// 20-2F --> collect, then switch to DCS_INTERMEDIATE
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
scanState = ScanState.DCS_INTERMEDIATE;
}
// 30-39, 3B --> param, then switch to DCS_PARAM
if ((ch >= '0') && (ch <= '9')) {
param((byte) ch);
scanState = ScanState.DCS_PARAM;
}
if (ch == ';') {
param((byte) ch);
scanState = ScanState.DCS_PARAM;
}
// 3C-3F --> collect, then switch to DCS_PARAM
if ((ch >= 0x3C) && (ch <= 0x3F)) {
collect(ch);
scanState = ScanState.DCS_PARAM;
}
// 00-17, 19, 1C-1F, 7F --> ignore
// 0x3A goes to DCS_IGNORE
if (ch == 0x3F) {
scanState = ScanState.DCS_IGNORE;
}
// 0x40-7E goes to DCS_PASSTHROUGH
if ((ch >= 0x40) && (ch <= 0x7E)) {
scanState = ScanState.DCS_PASSTHROUGH;
}
return;
case DCS_INTERMEDIATE:
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
// 0x1B 0x5C goes to GROUND
if (ch == 0x1B) {
collect(ch);
}
if (ch == 0x5C) {
if ((collectBuffer.length() > 0)
&& (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
) {
toGround();
}
}
// 0x30-3F goes to DCS_IGNORE
if ((ch >= 0x30) && (ch <= 0x3F)) {
scanState = ScanState.DCS_IGNORE;
}
// 0x40-7E goes to DCS_PASSTHROUGH
if ((ch >= 0x40) && (ch <= 0x7E)) {
scanState = ScanState.DCS_PASSTHROUGH;
}
// 00-17, 19, 1C-1F, 7F --> ignore
return;
case DCS_PARAM:
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
// 0x1B 0x5C goes to GROUND
if (ch == 0x1B) {
collect(ch);
}
if (ch == 0x5C) {
if ((collectBuffer.length() > 0)
&& (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
) {
toGround();
}
}
// 20-2F --> collect, then switch to DCS_INTERMEDIATE
if ((ch >= 0x20) && (ch <= 0x2F)) {
collect(ch);
scanState = ScanState.DCS_INTERMEDIATE;
}
// 30-39, 3B --> param
if ((ch >= '0') && (ch <= '9')) {
param((byte) ch);
}
if (ch == ';') {
param((byte) ch);
}
// 00-17, 19, 1C-1F, 7F --> ignore
// 0x3A, 3C-3F goes to DCS_IGNORE
if (ch == 0x3F) {
scanState = ScanState.DCS_IGNORE;
}
if ((ch >= 0x3C) && (ch <= 0x3F)) {
scanState = ScanState.DCS_IGNORE;
}
// 0x40-7E goes to DCS_PASSTHROUGH
if ((ch >= 0x40) && (ch <= 0x7E)) {
scanState = ScanState.DCS_PASSTHROUGH;
}
return;
case DCS_PASSTHROUGH:
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
// 0x1B 0x5C goes to GROUND
if (ch == 0x1B) {
collect(ch);
}
if (ch == 0x5C) {
if ((collectBuffer.length() > 0)
&& (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
) {
toGround();
}
}
// 00-17, 19, 1C-1F, 20-7E --> put
// TODO
if (ch <= 0x17) {
return;
}
if (ch == 0x19) {
return;
}
if ((ch >= 0x1C) && (ch <= 0x1F)) {
return;
}
if ((ch >= 0x20) && (ch <= 0x7E)) {
return;
}
// 7F --> ignore
return;
case DCS_IGNORE:
// 00-17, 19, 1C-1F, 20-7F --> ignore
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
return;
case SOSPMAPC_STRING:
// 00-17, 19, 1C-1F, 20-7F --> ignore
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
return;
case OSC_STRING:
// Special case for Xterm: OSC can pass control characters
if ((ch == 0x9C) || (ch <= 0x07)) {
oscPut(ch);
}
// 00-17, 19, 1C-1F --> ignore
// 20-7F --> osc_put
if ((ch >= 0x20) && (ch <= 0x7F)) {
oscPut(ch);
}
// 0x9C goes to GROUND
if (ch == 0x9C) {
toGround();
}
return;
case VT52_DIRECT_CURSOR_ADDRESS:
// This is a special case for the VT52 sequence "ESC Y l c"
if (collectBuffer.length() == 0) {
collect(ch);
} else if (collectBuffer.length() == 1) {
// We've got the two characters, one in the buffer and the
// other in ch.
cursorPosition(collectBuffer.charAt(0) - '\040', ch - '\040');
toGround();
}
return;
}
}
/**
* Expose current cursor X to outside world.
*
* @return current cursor X
*/
public final int getCursorX() {
if (display.get(currentState.cursorY).isDoubleWidth()) {
return currentState.cursorX * 2;
}
return currentState.cursorX;
}
/**
* Expose current cursor Y to outside world.
*
* @return current cursor Y
*/
public final int getCursorY() {
return currentState.cursorY;
}
/**
* Read function runs on a separate thread.
*/
public final void run() {
boolean utf8 = false;
boolean done = false;
if (type == DeviceType.XTERM) {
utf8 = true;
}
// available() will often return > 1, so we need to read in chunks to
// stay caught up.
char [] readBufferUTF8 = null;
byte [] readBuffer = null;
if (utf8) {
readBufferUTF8 = new char[128];
} else {
readBuffer = new byte[128];
}
while (!done && !stopReaderThread) {
try {
int n = inputStream.available();
// System.err.printf("available() %d\n", n); System.err.flush();
if (utf8) {
if (readBufferUTF8.length < n) {
// The buffer wasn't big enough, make it huger
int newSizeHalf = Math.max(readBufferUTF8.length,
n);
readBufferUTF8 = new char[newSizeHalf * 2];
}
} else {
if (readBuffer.length < n) {
// The buffer wasn't big enough, make it huger
int newSizeHalf = Math.max(readBuffer.length, n);
readBuffer = new byte[newSizeHalf * 2];
}
}
if (n == 0) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
// SQUASH
}
continue;
}
int rc = -1;
try {
if (utf8) {
rc = input.read(readBufferUTF8, 0,
readBufferUTF8.length);
} else {
rc = inputStream.read(readBuffer, 0,
readBuffer.length);
}
} catch (ReadTimeoutException e) {
rc = 0;
}
// System.err.printf("read() %d\n", rc); System.err.flush();
if (rc == -1) {
// This is EOF
done = true;
} else {
// Don't step on UI events
synchronized (this) {
for (int i = 0; i < rc; i++) {
int ch = 0;
if (utf8) {
ch = readBufferUTF8[i];
} else {
ch = readBuffer[i];
}
consume((char)ch);
}
}
// Permit my enclosing UI to know that I updated.
if (displayListener != null) {
displayListener.displayChanged();
}
}
// System.err.println("end while loop"); System.err.flush();
} catch (IOException e) {
e.printStackTrace();
done = true;
}
} // while ((done == false) && (stopReaderThread == false))
// Let the rest of the world know that I am done.
stopReaderThread = true;
try {
inputStream.cancelRead();
inputStream.close();
inputStream = null;
} catch (IOException e) {
// SQUASH
}
try {
input.close();
input = null;
} catch (IOException e) {
// SQUASH
}
// Permit my enclosing UI to know that I updated.
if (displayListener != null) {
displayListener.displayChanged();
}
// System.err.println("*** run() exiting..."); System.err.flush();
}
}