X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2Ftterminal%2FECMA48.java;h=537b2e0a4a3ba25238ee5d935f393aa07fcba24c;hb=8a1cae77d279cc246a68109f6178b2bb05e7471f;hp=8127458dc18e1ab8e319e88a43e786429005aaac;hpb=101bc589ae14b3258a0a09514e68a59bec2c3daa;p=fanfix.git diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java deleted file mode 100644 index 8127458..0000000 --- a/src/jexer/tterminal/ECMA48.java +++ /dev/null @@ -1,7343 +0,0 @@ -/* - * Jexer - Java Text User Interface - * - * The MIT License (MIT) - * - * Copyright (C) 2019 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.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.CharArrayWriter; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import javax.imageio.ImageIO; - -import jexer.TKeypress; -import jexer.backend.GlyphMaker; -import jexer.bits.Color; -import jexer.bits.Cell; -import jexer.bits.CellAttributes; -import jexer.bits.StringUtils; -import jexer.event.TInputEvent; -import jexer.event.TKeypressEvent; -import jexer.event.TMouseEvent; -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 { - - // ------------------------------------------------------------------------ - // Constants -------------------------------------------------------------- - // ------------------------------------------------------------------------ - - /** - * 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 - } - - /** - * Parser character scan states. - */ - private enum ScanState { - GROUND, - ESCAPE, - ESCAPE_INTERMEDIATE, - CSI_ENTRY, - CSI_PARAM, - CSI_INTERMEDIATE, - CSI_IGNORE, - DCS_ENTRY, - DCS_INTERMEDIATE, - DCS_PARAM, - DCS_PASSTHROUGH, - DCS_IGNORE, - DCS_SIXEL, - SOSPMAPC_STRING, - OSC_STRING, - VT52_DIRECT_CURSOR_ADDRESS - } - - /** - * 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. - */ - public enum MouseProtocol { - OFF, - X10, - NORMAL, - BUTTONEVENT, - ANYEVENT - } - - /** - * XTERM mouse reporting encodings. - */ - private enum MouseEncoding { - X10, - UTF8, - SGR - } - - // ------------------------------------------------------------------------ - // Variables -------------------------------------------------------------- - // ------------------------------------------------------------------------ - - /** - * 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; - - /** - * The type of emulator to be. - */ - private final DeviceType type; - - /** - * The scrollback buffer characters + attributes. - */ - private volatile ArrayList scrollback; - - /** - * The raw display buffer characters + attributes. - */ - private volatile ArrayList display; - - /** - * The maximum number of lines in the scrollback buffer. - */ - private int maxScrollback = 10000; - - /** - * 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; - - /** - * Current scanning state. - */ - private ScanState scanState; - - /** - * Which mouse protocol is active. - */ - private MouseProtocol mouseProtocol = MouseProtocol.OFF; - - /** - * Which mouse encoding is active. - */ - private MouseEncoding mouseEncoding = MouseEncoding.X10; - - /** - * A terminal may request that the mouse pointer be hidden using a - * Privacy Message containing either "hideMousePointer" or - * "showMousePointer". This is currently only used within Jexer by - * TTerminalWindow so that only the bottom-most instance of nested - * Jexer's draws the mouse within its application window. - */ - private boolean hideMousePointer = false; - - /** - * Physical display width. We start at 80x24, but the user can resize us - * bigger/smaller. - */ - private int width = 80; - - /** - * Physical display height. We start at 80x24, but the user can resize - * us bigger/smaller. - */ - private int height = 24; - - /** - * Top margin of the scrolling region. - */ - private int scrollRegionTop = 0; - - /** - * Bottom margin of the scrolling region. - */ - private int scrollRegionBottom = height - 1; - - /** - * 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 = 79; - - /** - * Last character printed. - */ - private int 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 = false; - - /** - * 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; - - /** - * 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 = ""; - - /** - * Parameter characters being collected. - */ - private List csiParams; - - /** - * Non-csi collect buffer. - */ - private StringBuilder collectBuffer = new StringBuilder(128); - - /** - * 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; - - /** - * true = reverse video. Set by DECSCNM. - */ - private boolean reverseVideo = false; - - /** - * false = echo characters locally. - */ - private boolean fullDuplex = true; - - /** - * The current terminal state. - */ - private SaveableState currentState; - - /** - * The last saved terminal state. - */ - private SaveableState savedState; - - /** - * The 88- or 256-color support RGB colors. - */ - private List colors88; - - /** - * Sixel collection buffer. - */ - private StringBuilder sixelParseBuffer = new StringBuilder(2048); - - /** - * Sixel shared palette. - */ - private HashMap sixelPalette; - - /** - * The width of a character cell in pixels. - */ - private int textWidth = 16; - - /** - * The height of a character cell in pixels. - */ - private int textHeight = 20; - - /** - * The last used height of a character cell in pixels, only used for - * full-width chars. - */ - private int lastTextHeight = -1; - - /** - * The glyph drawer for full-width chars. - */ - private GlyphMaker glyphMaker = null; - - /** - * Input queue for keystrokes and mouse events to send to the remote - * side. - */ - private ArrayList userQueue = new ArrayList(); - - /** - * 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(); - } - } - - // ------------------------------------------------------------------------ - // Constructors ----------------------------------------------------------- - // ------------------------------------------------------------------------ - - /** - * 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 ArrayList(); - display = new ArrayList(); - - 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(new BufferedInputStream( - this.inputStream, 1024 * 128), "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)); - } - assert (currentState.cursorY < height); - assert (currentState.cursorX < width); - - // Spin up the input reader - readerThread = new Thread(this); - readerThread.start(); - } - - // ------------------------------------------------------------------------ - // Runnable --------------------------------------------------------------- - // ------------------------------------------------------------------------ - - /** - * 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[2048]; - } else { - readBuffer = new byte[2048]; - } - - while (!done && !stopReaderThread) { - synchronized (userQueue) { - while (userQueue.size() > 0) { - handleUserEvent(userQueue.remove(0)); - } - } - - 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(10); - } 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) { - if (utf8) { - for (int i = 0; i < rc;) { - int ch = Character.codePointAt(readBufferUTF8, - i); - i += Character.charCount(ch); - - // Special case for VT10x: 7-bit characters - // only. - if ((type == DeviceType.VT100) - || (type == DeviceType.VT102) - ) { - consume(ch & 0x7F); - } else { - consume(ch); - } - } - } else { - for (int i = 0; i < rc; i++) { - // Special case for VT10x: 7-bit characters - // only. - if ((type == DeviceType.VT100) - || (type == DeviceType.VT102) - ) { - consume(readBuffer[i] & 0x7F); - } else { - consume(readBuffer[i]); - } - } - } - } - // 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) { - done = true; - - // This is an unusual case. We want to see the stack trace, - // but it is related to the spawned process rather than the - // actual UI. We will generate the stack trace, and consume - // it as though it was emitted by the shell. - CharArrayWriter writer= new CharArrayWriter(); - // Send a ST and RIS to clear the emulator state. - try { - writer.write("\033\\\033c"); - writer.write("\n-----------------------------------\n"); - e.printStackTrace(new PrintWriter(writer)); - writer.write("\n-----------------------------------\n"); - } catch (IOException e2) { - // SQUASH - } - char [] stackTrace = writer.toCharArray(); - for (int i = 0; i < stackTrace.length; i++) { - if (stackTrace[i] == '\n') { - consume('\r'); - } - consume(stackTrace[i]); - } - } - - } // 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(); - } - - // ------------------------------------------------------------------------ - // ECMA48 ----------------------------------------------------------------- - // ------------------------------------------------------------------------ - - /** - * Process keyboard and mouse events from the user. - * - * @param event the input event to consume - */ - private void handleUserEvent(final TInputEvent event) { - if (event instanceof TKeypressEvent) { - keypress(((TKeypressEvent) event).getKey()); - } - if (event instanceof TMouseEvent) { - mouse((TMouseEvent) event); - } - } - - /** - * Add a keyboard and mouse event from the user to the queue. - * - * @param event the input event to consume - */ - public void addUserEvent(final TInputEvent event) { - synchronized (userQueue) { - userQueue.add(event); - } - } - - /** - * 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, with sixel and Jexer image - // support. - if (!s8c1t) { - return "\033[?62;1;6;9;4;22;444c"; - } - // "I am a VT220" - 8 bit version, with sixel and Jexer image - // support. - return "\u009b?62;1;6;9;4;22;444c"; - 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) { - // SQUASH - } - } - - // 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); - } - } - - /** - * 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); - } - - /** - * 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); - } - - /** - * Get the scrollback buffer. - * - * @return the scrollback buffer - */ - public final List getScrollbackBuffer() { - return scrollback; - } - - /** - * Get the display buffer. - * - * @return the display buffer - */ - public final List getDisplayBuffer() { - return display; - } - - /** - * Get the visible display + scrollback buffer, offset by a specified - * number of rows from the bottom. - * - * @param visibleHeight the total height of the display to show - * @param scrollBottom the number of rows from the bottom to scroll back - * @return a copy of the display + scrollback buffers - */ - public final List getVisibleDisplay(final int visibleHeight, - final int scrollBottom) { - - assert (visibleHeight >= 0); - assert (scrollBottom >= 0); - - int visibleBottom = scrollback.size() + display.size() - scrollBottom; - - List preceedingBlankLines = new ArrayList(); - int visibleTop = visibleBottom - visibleHeight; - if (visibleTop < 0) { - for (int i = visibleTop; i < 0; i++) { - preceedingBlankLines.add(getBlankDisplayLine()); - } - visibleTop = 0; - } - assert (visibleTop >= 0); - - List displayLines = new ArrayList(); - displayLines.addAll(scrollback); - displayLines.addAll(display); - - List visibleLines = new ArrayList(); - visibleLines.addAll(preceedingBlankLines); - visibleLines.addAll(displayLines.subList(visibleTop, visibleBottom)); - - // Fill in the blank lines on bottom - int bottomBlankLines = visibleHeight - visibleLines.size(); - assert (bottomBlankLines >= 0); - for (int i = 0; i < bottomBlankLines; i++) { - visibleLines.add(getBlankDisplayLine()); - } - - return copyBuffer(visibleLines); - } - - /** - * Copy a display buffer. - * - * @param buffer the buffer to copy - * @return a deep copy of the buffer's data - */ - private List copyBuffer(final List buffer) { - ArrayList result = new ArrayList(buffer.size()); - for (DisplayLine line: buffer) { - result.add(new DisplayLine(line)); - } - return result; - } - - /** - * 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 synchronized 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; - } - } - - /** - * 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 synchronized void setHeight(final int height) { - int delta = height - this.height; - this.height = height; - scrollRegionBottom += delta; - if ((scrollRegionBottom < 0) || (scrollRegionTop > height - 1)) { - scrollRegionBottom = height - 1; - } - 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)); - } - } - - /** - * Get visible cursor flag. - * - * @return if true, the cursor is visible - */ - public final boolean isCursorVisible() { - return cursorVisible; - } - - /** - * 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; - } - - /** - * Get 132 columns value. - * - * @return if true, the terminal is in 132 column mode - */ - public final boolean isColumns132() { - return columns132; - } - - /** - * Clear the CSI parameters and flags. - */ - private void toGround() { - csiParams.clear(); - collectBuffer.setLength(0); - scanState = ScanState.GROUND; - } - - /** - * Reset the tab stops list. - */ - private void resetTabStops() { - tabStops.clear(); - for (int i = 0; (i * 8) <= rightMargin; i++) { - tabStops.add(Integer.valueOf(i * 8)); - } - } - - /** - * Reset the 88- or 256-colors. - */ - private void resetColors() { - colors88 = new ArrayList(256); - for (int i = 0; i < 256; i++) { - colors88.add(0); - } - - // Set default system colors. - colors88.set(0, 0x00000000); - colors88.set(1, 0x00a80000); - colors88.set(2, 0x0000a800); - colors88.set(3, 0x00a85400); - colors88.set(4, 0x000000a8); - colors88.set(5, 0x00a800a8); - colors88.set(6, 0x0000a8a8); - colors88.set(7, 0x00a8a8a8); - - colors88.set(8, 0x00545454); - colors88.set(9, 0x00fc5454); - colors88.set(10, 0x0054fc54); - colors88.set(11, 0x00fcfc54); - colors88.set(12, 0x005454fc); - colors88.set(13, 0x00fc54fc); - colors88.set(14, 0x0054fcfc); - colors88.set(15, 0x00fcfcfc); - } - - /** - * Get the RGB value of one of the indexed colors. - * - * @param index the color index - * @return the RGB value - */ - private int get88Color(final int index) { - // System.err.print("get88Color: " + index); - if ((index < 0) || (index > colors88.size())) { - // System.err.println(" -- UNKNOWN"); - return 0; - } - // System.err.printf(" %08x\n", colors88.get(index)); - return colors88.get(index); - } - - /** - * Set one of the indexed colors to a color specification. - * - * @param index the color index - * @param spec the specification, typically something like "rgb:aa/bb/cc" - */ - private void set88Color(final int index, final String spec) { - // System.err.println("set88Color: " + index + " '" + spec + "'"); - - if ((index < 0) || (index > colors88.size())) { - return; - } - if (spec.startsWith("rgb:")) { - String [] rgbTokens = spec.substring(4).split("/"); - if (rgbTokens.length == 3) { - try { - int rgb = (Integer.parseInt(rgbTokens[0], 16) << 16); - rgb |= Integer.parseInt(rgbTokens[1], 16) << 8; - rgb |= Integer.parseInt(rgbTokens[2], 16); - // System.err.printf(" set to %08x\n", rgb); - colors88.set(index, rgb); - } catch (NumberFormatException e) { - // SQUASH - } - } - return; - } - - if (spec.toLowerCase().equals("black")) { - colors88.set(index, 0x00000000); - } else if (spec.toLowerCase().equals("red")) { - colors88.set(index, 0x00a80000); - } else if (spec.toLowerCase().equals("green")) { - colors88.set(index, 0x0000a800); - } else if (spec.toLowerCase().equals("yellow")) { - colors88.set(index, 0x00a85400); - } else if (spec.toLowerCase().equals("blue")) { - colors88.set(index, 0x000000a8); - } else if (spec.toLowerCase().equals("magenta")) { - colors88.set(index, 0x00a800a8); - } else if (spec.toLowerCase().equals("cyan")) { - colors88.set(index, 0x0000a8a8); - } else if (spec.toLowerCase().equals("white")) { - colors88.set(index, 0x00a8a8a8); - } - - } - - /** - * Reset the emulation state. - */ - private void reset() { - - currentState = new SaveableState(); - savedState = new SaveableState(); - scanState = ScanState.GROUND; - if (displayListener != null) { - width = displayListener.getDisplayWidth(); - height = displayListener.getDisplayHeight(); - } else { - width = 80; - height = 24; - } - scrollRegionTop = 0; - scrollRegionBottom = height - 1; - rightMargin = width - 1; - newLineMode = false; - arrowKeyMode = ArrowKeyMode.ANSI; - keypadMode = KeypadMode.Numeric; - wrapLineFlag = false; - - // 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(); - - // Reset extra colors - resetColors(); - - // Clear CSI stuff - toGround(); - } - - /** - * 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)); - if (scrollback.size() > maxScrollback) { - scrollback.remove(0); - scrollback.trimToSize(); - } - display.remove(0); - display.trimToSize(); - 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 int ch) { - int rightMargin = this.rightMargin; - - if (StringUtils.width(ch) == 2) { - // This is a full-width character. Save two spaces, and then - // draw the character as two image halves. - int x0 = currentState.cursorX; - int y0 = currentState.cursorY; - printCharacter(' '); - printCharacter(' '); - if ((currentState.cursorX == x0 + 2) - && (currentState.cursorY == y0) - ) { - // We can draw both halves of the character. - drawHalves(x0, y0, x0 + 1, y0, ch); - } else if ((currentState.cursorX == x0 + 1) - && (currentState.cursorY == y0) - ) { - // VT100 line wrap behavior: we should be at the right - // margin. We can draw both halves of the character. - drawHalves(x0, y0, x0 + 1, y0, ch); - } else { - // The character splits across the line. Draw the entire - // character on the new line, giving one more space for it. - x0 = currentState.cursorX - 1; - y0 = currentState.cursorY; - printCharacter(' '); - drawHalves(x0, y0, x0 + 1, y0, ch); - } - return; - } - - // 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); - - if (StringUtils.width(ch) == 1) { - // 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 - */ - private 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("[<"); - int buttons = 0; - - if (mouse.isMouse1()) { - if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { - buttons = 32; - } else { - buttons = 0; - } - } else if (mouse.isMouse2()) { - if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { - buttons = 33; - } else { - buttons = 1; - } - } else if (mouse.isMouse3()) { - if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { - buttons = 34; - } else { - buttons = 2; - } - } else if (mouse.isMouseWheelUp()) { - buttons = 64; - } else if (mouse.isMouseWheelDown()) { - buttons = 65; - } else { - // This is motion with no buttons down. - buttons = 35; - } - if (mouse.isAlt()) { - buttons |= 0x08; - } - if (mouse.isCtrl()) { - buttons |= 0x10; - } - if (mouse.isShift()) { - buttons |= 0x04; - } - - sb.append(String.format("%d;%d;%d", buttons, 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'); - int buttons = 0; - if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) { - buttons = 0x03 + 32; - } else if (mouse.isMouse1()) { - if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { - buttons = 0x00 + 32 + 32; - } else { - buttons = 0x00 + 32; - } - } else if (mouse.isMouse2()) { - if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { - buttons = 0x01 + 32 + 32; - } else { - buttons = 0x01 + 32; - } - } else if (mouse.isMouse3()) { - if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) { - buttons = 0x02 + 32 + 32; - } else { - buttons = 0x02 + 32; - } - } else if (mouse.isMouseWheelUp()) { - buttons = 0x04 + 64; - } else if (mouse.isMouseWheelDown()) { - buttons = 0x05 + 64; - } else { - // This is motion with no buttons down. - buttons = 0x03 + 32; - } - if (mouse.isAlt()) { - buttons |= 0x08; - } - if (mouse.isCtrl()) { - buttons |= 0x10; - } - if (mouse.isShift()) { - buttons |= 0x04; - } - - sb.append((char) (buttons & 0xFF)); - 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 - */ - private 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 - */ - @SuppressWarnings("fallthrough") - 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((char) keypress.getChar()); - } else { - // Local echo for everything else - printCharacter(keypress.getChar()); - } - if (displayListener != null) { - displayListener.displayChanged(); - } - } - - if ((newLineMode == true) && (keypress.equals(kbEnter))) { - // NLM: send CRLF - return "\015\012"; - } - - // Handle control characters - if ((keypress.isCtrl()) && (!keypress.isFnKey())) { - StringBuilder sb = new StringBuilder(); - int ch = keypress.getChar(); - ch -= 0x40; - sb.append(Character.toChars(ch)); - return sb.toString(); - } - - // Handle alt characters - if ((keypress.isAlt()) && (!keypress.isFnKey())) { - StringBuilder sb = new StringBuilder("\033"); - int ch = keypress.getChar(); - sb.append(Character.toChars(ch)); - return sb.toString(); - } - - if (keypress.equals(kbBackspaceDel)) { - 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(Character.toChars(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 int 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 int mapCharacter(final int 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: - throw new IllegalArgumentException("programming bug"); - - case G2_GR: - throw new IllegalArgumentException("programming bug"); - - case G3_GR: - throw new IllegalArgumentException("programming bug"); - - 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: - throw new IllegalArgumentException("programming bug"); - - case G3_GL: - throw new IllegalArgumentException("programming bug"); - - 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 ArrayList(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 ArrayList(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(Integer.valueOf(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.size() < 16)) { - csiParams.add(Integer.valueOf(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 80: - if (type == DeviceType.XTERM) { - if (decPrivateModeFlag == true) { - if (value == true) { - // Enable sixel scrolling (default). - // Not supported - } else { - // Disable sixel scrolling. - // 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; - - case 1070: - if (type == DeviceType.XTERM) { - if (decPrivateModeFlag == true) { - if (value == true) { - // Use private color registers for each sixel - // graphic (default). - sixelPalette = null; - } else { - // Use shared color registers for each sixel - // graphic. - sixelPalette = new HashMap(); - } - } - } - 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('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; - } - - int sgrColorMode = -1; - boolean idx88Color = false; - boolean rgbColor = false; - int rgbRed = -1; - int rgbGreen = -1; - - for (Integer i: csiParams) { - - if ((sgrColorMode == 38) || (sgrColorMode == 48)) { - - assert (type == DeviceType.XTERM); - - if (idx88Color) { - /* - * Indexed color mode, we now have the index number. - */ - if (sgrColorMode == 38) { - currentState.attr.setForeColorRGB(get88Color(i)); - } else { - assert (sgrColorMode == 48); - currentState.attr.setBackColorRGB(get88Color(i)); - } - sgrColorMode = -1; - idx88Color = false; - continue; - } - - if (rgbColor) { - /* - * RGB color mode, we are collecting tokens. - */ - if (rgbRed == -1) { - rgbRed = i & 0xFF; - } else if (rgbGreen == -1) { - rgbGreen = i & 0xFF; - } else { - int rgb = rgbRed << 16; - rgb |= rgbGreen << 8; - rgb |= i & 0xFF; - - // System.err.printf("RGB: %08x\n", rgb); - - if (sgrColorMode == 38) { - currentState.attr.setForeColorRGB(rgb); - } else { - assert (sgrColorMode == 48); - currentState.attr.setBackColorRGB(rgb); - } - rgbRed = -1; - rgbGreen = -1; - sgrColorMode = -1; - rgbColor = false; - } - continue; - } - - switch (i) { - - case 2: - /* - * RGB color mode. - */ - rgbColor = true; - break; - - case 5: - /* - * Indexed color mode. - */ - idx88Color = true; - break; - - default: - /* - * This is neither indexed nor RGB color. Bail out. - */ - return; - } - - } // if ((sgrColorMode == 38) || (sgrColorMode == 48)) - - 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 - // Not supported - break; - - case 90: - // Set black foreground - currentState.attr.setForeColorRGB(get88Color(8)); - break; - case 91: - // Set red foreground - currentState.attr.setForeColorRGB(get88Color(9)); - break; - case 92: - // Set green foreground - currentState.attr.setForeColorRGB(get88Color(10)); - break; - case 93: - // Set yellow foreground - currentState.attr.setForeColorRGB(get88Color(11)); - break; - case 94: - // Set blue foreground - currentState.attr.setForeColorRGB(get88Color(12)); - break; - case 95: - // Set magenta foreground - currentState.attr.setForeColorRGB(get88Color(13)); - break; - case 96: - // Set cyan foreground - currentState.attr.setForeColorRGB(get88Color(14)); - break; - case 97: - // Set white foreground - currentState.attr.setForeColorRGB(get88Color(15)); - break; - - case 100: - // Set black background - currentState.attr.setBackColorRGB(get88Color(8)); - break; - case 101: - // Set red background - currentState.attr.setBackColorRGB(get88Color(9)); - break; - case 102: - // Set green background - currentState.attr.setBackColorRGB(get88Color(10)); - break; - case 103: - // Set yellow background - currentState.attr.setBackColorRGB(get88Color(11)); - break; - case 104: - // Set blue background - currentState.attr.setBackColorRGB(get88Color(12)); - break; - case 105: - // Set magenta background - currentState.attr.setBackColorRGB(get88Color(13)); - break; - case 106: - // Set cyan background - currentState.attr.setBackColorRGB(get88Color(14)); - break; - case 107: - // Set white background - currentState.attr.setBackColorRGB(get88Color(15)); - 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); - currentState.attr.setForeColorRGB(-1); - break; - case 31: - // Set red foreground - currentState.attr.setForeColor(Color.RED); - currentState.attr.setForeColorRGB(-1); - break; - case 32: - // Set green foreground - currentState.attr.setForeColor(Color.GREEN); - currentState.attr.setForeColorRGB(-1); - break; - case 33: - // Set yellow foreground - currentState.attr.setForeColor(Color.YELLOW); - currentState.attr.setForeColorRGB(-1); - break; - case 34: - // Set blue foreground - currentState.attr.setForeColor(Color.BLUE); - currentState.attr.setForeColorRGB(-1); - break; - case 35: - // Set magenta foreground - currentState.attr.setForeColor(Color.MAGENTA); - currentState.attr.setForeColorRGB(-1); - break; - case 36: - // Set cyan foreground - currentState.attr.setForeColor(Color.CYAN); - currentState.attr.setForeColorRGB(-1); - break; - case 37: - // Set white foreground - currentState.attr.setForeColor(Color.WHITE); - currentState.attr.setForeColorRGB(-1); - 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 support only the following: - * - * 1. Indexed color mode (88- or 256-color modes). - * - * 2. Direct RGB. - * - * These cover most of the use cases in the real world. - * - * HOWEVER, note that this is an awful broken "standard", - * with no way to do it "right". See - * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors - * for 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". - * - * Also see - * https://bugs.kde.org/show_bug.cgi?id=107487#c3 . - * where it is assumed that 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. - */ - sgrColorMode = 38; - continue; - } 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); - currentState.attr.setForeColorRGB(-1); - break; - case 40: - // Set black background - currentState.attr.setBackColor(Color.BLACK); - currentState.attr.setBackColorRGB(-1); - break; - case 41: - // Set red background - currentState.attr.setBackColor(Color.RED); - currentState.attr.setBackColorRGB(-1); - break; - case 42: - // Set green background - currentState.attr.setBackColor(Color.GREEN); - currentState.attr.setBackColorRGB(-1); - break; - case 43: - // Set yellow background - currentState.attr.setBackColor(Color.YELLOW); - currentState.attr.setBackColorRGB(-1); - break; - case 44: - // Set blue background - currentState.attr.setBackColor(Color.BLUE); - currentState.attr.setBackColorRGB(-1); - break; - case 45: - // Set magenta background - currentState.attr.setBackColor(Color.MAGENTA); - currentState.attr.setBackColorRGB(-1); - break; - case 46: - // Set cyan background - currentState.attr.setBackColor(Color.CYAN); - currentState.attr.setBackColorRGB(-1); - break; - case 47: - // Set white background - currentState.attr.setBackColor(Color.WHITE); - currentState.attr.setBackColorRGB(-1); - 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 support only the following: - * - * 1. Indexed color mode (88- or 256-color modes). - * - * 2. Direct RGB. - * - * These cover most of the use cases in the real world. - * - * HOWEVER, note that this is an awful broken "standard", - * with no way to do it "right". See - * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors - * for 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". - * - * Also see - * https://bugs.kde.org/show_bug.cgi?id=107487#c3 . - * where it is assumed that 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. - */ - sgrColorMode = 48; - continue; - } - break; - case 49: - // Default background - currentState.attr.setBackColor(Color.BLACK); - currentState.attr.setBackColorRGB(-1); - 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); - } - } else if (collectBuffer.charAt(0) == '=') { - extendedFlag = 2; - if (collectBuffer.length() >= 2) { - i = Integer.parseInt(args); - } - } 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 (bottom > height - 1) { - bottom = 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) { - // System.err.println("oscPut: " + xtermChar); - - // Collect first - collectBuffer.append(xtermChar); - - // Xterm cases... - if ((xtermChar == 0x07) - || (collectBuffer.toString().endsWith("\033\\")) - ) { - String args = null; - if (xtermChar == 0x07) { - args = collectBuffer.substring(0, collectBuffer.length() - 1); - } else { - args = collectBuffer.substring(0, collectBuffer.length() - 2); - } - - String [] p = args.split(";"); - if (p.length > 0) { - if ((p[0].equals("0")) || (p[0].equals("2"))) { - if (p.length > 1) { - // Screen title - screenTitle = p[1]; - } - } - - if (p[0].equals("4")) { - for (int i = 1; i + 1 < p.length; i += 2) { - // Set a color index value - try { - set88Color(Integer.parseInt(p[i]), p[i + 1]); - } catch (NumberFormatException e) { - // SQUASH - } - } - } - - if (p[0].equals("10")) { - if (p[1].equals("?")) { - // Respond with foreground color. - java.awt.Color color = jexer.backend.SwingTerminal.attrToForegroundColor(currentState.attr); - - writeRemote(String.format( - "\033]10;rgb:%04x/%04x/%04x\033\\", - color.getRed() << 8, - color.getGreen() << 8, - color.getBlue() << 8)); - } - } - - if (p[0].equals("11")) { - if (p[1].equals("?")) { - // Respond with background color. - java.awt.Color color = jexer.backend.SwingTerminal.attrToBackgroundColor(currentState.attr); - - writeRemote(String.format( - "\033]11;rgb:%04x/%04x/%04x\033\\", - color.getRed() << 8, - color.getGreen() << 8, - color.getBlue() << 8)); - } - } - - if (p[0].equals("444")) { - if (p[1].equals("0") && (p.length == 6)) { - // Jexer image - RGB - parseJexerImageRGB(p[2], p[3], p[4], p[5]); - } else if (p[1].equals("1") && (p.length == 4)) { - // Jexer image - PNG - parseJexerImageFile(1, p[2], p[3]); - } else if (p[1].equals("2") && (p.length == 4)) { - // Jexer image - JPG - parseJexerImageFile(2, p[2], p[3]); - } - } - } - - // Go to SCAN_GROUND state - toGround(); - return; - } - } - - /** - * Handle the SCAN_SOSPMAPC_STRING state. This is currently only used by - * Jexer ECMA48Terminal to talk to ECMA48. - * - * @param pmChar the character received from the remote side - */ - private void pmPut(final char pmChar) { - // System.err.println("pmPut: " + pmChar); - - // Collect first - collectBuffer.append(pmChar); - - // Xterm cases... - if (collectBuffer.toString().endsWith("\033\\")) { - String arg = null; - arg = collectBuffer.substring(0, collectBuffer.length() - 2); - - // System.err.println("arg: '" + arg + "'"); - - if (arg.equals("hideMousePointer")) { - hideMousePointer = true; - } - if (arg.equals("showMousePointer")) { - hideMousePointer = false; - } - - // Go to SCAN_GROUND state - toGround(); - return; - } - } - - /** - * Perform xterm window operations. - */ - private void xtermWindowOps() { - boolean xtermPrivateModeFlag = false; - - for (int i = 0; i < collectBuffer.length(); i++) { - if (collectBuffer.charAt(i) == '?') { - xtermPrivateModeFlag = true; - break; - } - } - - int i = getCsiParam(0, 0); - - if (!xtermPrivateModeFlag) { - switch (i) { - case 14: - // Report xterm text area size in pixels as CSI 4 ; height ; - // width t - writeRemote(String.format("\033[4;%d;%dt", textHeight * height, - textWidth * width)); - break; - case 16: - // Report character size in pixels as CSI 6 ; height ; width - // t - writeRemote(String.format("\033[6;%d;%dt", textHeight, - textWidth)); - break; - case 18: - // Report the text are size in characters as CSI 8 ; height ; - // width t - writeRemote(String.format("\033[8;%d;%dt", height, width)); - break; - default: - break; - } - } - } - - /** - * Respond to xterm sixel query. - */ - private void xtermSixelQuery() { - int item = getCsiParam(0, 0); - int action = getCsiParam(1, 0); - int value = getCsiParam(2, 0); - - switch (item) { - case 1: - if (action == 1) { - // Report number of color registers. - writeRemote(String.format("\033[?%d;%d;%dS", item, 0, 1024)); - return; - } - break; - default: - break; - } - // We will not support this option. - writeRemote(String.format("\033[?%d;%dS", item, action)); - } - - /** - * Run this input character through the ECMA48 state machine. - * - * @param ch character from the remote side - */ - private void consume(final int ch) { - - // DEBUG - // System.err.printf("%c STATE = %s\n", ch, scanState); - - // 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) { - if ((type == DeviceType.XTERM) - && ((scanState == ScanState.OSC_STRING) - || (scanState == ScanState.DCS_SIXEL) - || (scanState == ScanState.SOSPMAPC_STRING)) - ) { - // Xterm can pass ESCAPE to its OSC sequence. - // Xterm can pass ESCAPE to its DCS sequence. - // Jexer can pass ESCAPE to its PM sequence. - } else if ((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((char) 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((char) ch); - return; - } - - // 20-2F --> collect, then switch to ESCAPE_INTERMEDIATE - if ((ch >= 0x20) && (ch <= 0x2F)) { - collect((char) 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((char) ch); - } - - // 20-2F --> collect - if ((ch >= 0x20) && (ch <= 0x2F)) { - collect((char) 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((char) ch); - } - - // 20-2F --> collect, then switch to CSI_INTERMEDIATE - if ((ch >= 0x20) && (ch <= 0x2F)) { - collect((char) 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((char) 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) { - boolean xtermPrivateModeFlag = false; - for (int i = 0; i < collectBuffer.length(); i++) { - if (collectBuffer.charAt(i) == '?') { - xtermPrivateModeFlag = true; - break; - } - } - if (xtermPrivateModeFlag) { - xtermSixelQuery(); - } else { - 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': - if (type == DeviceType.XTERM) { - // Window operations - xtermWindowOps(); - } - 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((char) ch); - } - - // 20-2F --> collect, then switch to CSI_INTERMEDIATE - if ((ch >= 0x20) && (ch <= 0x2F)) { - collect((char) 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) { - boolean xtermPrivateModeFlag = false; - for (int i = 0; i < collectBuffer.length(); i++) { - if (collectBuffer.charAt(i) == '?') { - xtermPrivateModeFlag = true; - break; - } - } - if (xtermPrivateModeFlag) { - xtermSixelQuery(); - } else { - 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': - break; - case 't': - if (type == DeviceType.XTERM) { - // Window operations - xtermWindowOps(); - } - break; - 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((char) ch); - } - - // 20-2F --> collect - if ((ch >= 0x20) && (ch <= 0x2F)) { - collect((char) 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((char) ch); - } - - // 20-2F --> collect - if ((ch >= 0x20) && (ch <= 0x2F)) { - collect((char) 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((char) 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((char) 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((char) ch); - scanState = ScanState.DCS_PARAM; - } - - // 00-17, 19, 1C-1F, 7F --> ignore - - // 0x3A goes to DCS_IGNORE - if (ch == 0x3F) { - scanState = ScanState.DCS_IGNORE; - } - - // 0x71 goes to DCS_SIXEL - if (ch == 0x71) { - sixelParseBuffer.setLength(0); - scanState = ScanState.DCS_SIXEL; - } else if ((ch >= 0x40) && (ch <= 0x7E)) { - // 0x40-7E goes to DCS_PASSTHROUGH - 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((char) 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((char) 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((char) 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; - } - - // 0x71 goes to DCS_SIXEL - if (ch == 0x71) { - sixelParseBuffer.setLength(0); - scanState = ScanState.DCS_SIXEL; - } else if ((ch >= 0x40) && (ch <= 0x7E)) { - // 0x40-7E goes to DCS_PASSTHROUGH - 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((char) ch); - } - if (ch == 0x5C) { - if ((collectBuffer.length() > 0) - && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B) - ) { - toGround(); - } - } - - // 00-17, 19, 1C-1F, 20-7E --> put - if (ch <= 0x17) { - // We ignore all DCS except sixel. - return; - } - if (ch == 0x19) { - // We ignore all DCS except sixel. - return; - } - if ((ch >= 0x1C) && (ch <= 0x1F)) { - // We ignore all DCS except sixel. - return; - } - if ((ch >= 0x20) && (ch <= 0x7E)) { - // We ignore all DCS except sixel. - 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 DCS_SIXEL: - // 0x9C goes to GROUND - if (ch == 0x9C) { - parseSixel(); - toGround(); - return; - } - - // 0x1B 0x5C goes to GROUND - if (ch == 0x1B) { - collect((char) ch); - return; - } - if (ch == 0x5C) { - if ((collectBuffer.length() > 0) - && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B) - ) { - parseSixel(); - toGround(); - return; - } - } - - // 00-17, 19, 1C-1F, 20-7E --> put - if ((ch <= 0x17) - || (ch == 0x19) - || ((ch >= 0x1C) && (ch <= 0x1F)) - || ((ch >= 0x20) && (ch <= 0x7E)) - ) { - sixelParseBuffer.append((char) ch); - } - - // 7F --> ignore - return; - - case SOSPMAPC_STRING: - // 00-17, 19, 1C-1F, 20-7F --> ignore - - // Special case for Jexer: PM can pass one control character - if (ch == 0x1B) { - pmPut((char) ch); - } - - if ((ch >= 0x20) && (ch <= 0x7F)) { - pmPut((char) ch); - } - - // 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) || (ch == 0x1B)) { - oscPut((char) ch); - } - - // 00-17, 19, 1C-1F --> ignore - - // 20-7F --> osc_put - if ((ch >= 0x20) && (ch <= 0x7F)) { - oscPut((char) 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((char) 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; - } - - /** - * Returns true if this terminal has requested the mouse pointer be - * hidden. - * - * @return true if this terminal has requested the mouse pointer be - * hidden - */ - public final boolean hasHiddenMousePointer() { - return hideMousePointer; - } - - /** - * Get the mouse protocol. - * - * @return MouseProtocol.OFF, MouseProtocol.X10, etc. - */ - public MouseProtocol getMouseProtocol() { - return mouseProtocol; - } - - /** - * Draw the left and right cells of a two-cell-wide (full-width) glyph. - * - * @param leftX the x position to draw the left half to - * @param leftY the y position to draw the left half to - * @param rightX the x position to draw the right half to - * @param rightY the y position to draw the right half to - * @param ch the character to draw - */ - private void drawHalves(final int leftX, final int leftY, - final int rightX, final int rightY, final int ch) { - - // System.err.println("drawHalves(): " + Integer.toHexString(ch)); - - if (lastTextHeight != textHeight) { - glyphMaker = GlyphMaker.getInstance(textHeight); - lastTextHeight = textHeight; - } - - Cell cell = new Cell(ch, currentState.attr); - BufferedImage image = glyphMaker.getImage(cell, textWidth * 2, - textHeight); - BufferedImage leftImage = image.getSubimage(0, 0, textWidth, - textHeight); - BufferedImage rightImage = image.getSubimage(textWidth, 0, textWidth, - textHeight); - - Cell left = new Cell(cell); - left.setImage(leftImage); - left.setWidth(Cell.Width.LEFT); - display.get(leftY).replace(leftX, left); - - Cell right = new Cell(cell); - right.setImage(rightImage); - right.setWidth(Cell.Width.RIGHT); - display.get(rightY).replace(rightX, right); - } - - /** - * Set the width of a character cell in pixels. - * - * @param textWidth the width in pixels of a character cell - */ - public void setTextWidth(final int textWidth) { - this.textWidth = textWidth; - } - - /** - * Set the height of a character cell in pixels. - * - * @param textHeight the height in pixels of a character cell - */ - public void setTextHeight(final int textHeight) { - this.textHeight = textHeight; - } - - /** - * Parse a sixel string into a bitmap image, and overlay that image onto - * the text cells. - */ - private void parseSixel() { - - /* - System.err.println("parseSixel(): '" + sixelParseBuffer.toString() - + "'"); - */ - - Sixel sixel = new Sixel(sixelParseBuffer.toString(), sixelPalette); - BufferedImage image = sixel.getImage(); - - // System.err.println("parseSixel(): image " + image); - - if (image == null) { - // Sixel data was malformed in some way, bail out. - return; - } - if ((image.getWidth() < 1) - || (image.getWidth() > 10000) - || (image.getHeight() < 1) - || (image.getHeight() > 10000) - ) { - return; - } - - imageToCells(image, true); - } - - /** - * Parse a "Jexer" RGB image string into a bitmap image, and overlay that - * image onto the text cells. - * - * @param pw width token - * @param ph height token - * @param ps scroll token - * @param data pixel data - */ - private void parseJexerImageRGB(final String pw, final String ph, - final String ps, final String data) { - - int imageWidth = 0; - int imageHeight = 0; - boolean scroll = false; - try { - imageWidth = Integer.parseInt(pw); - imageHeight = Integer.parseInt(ph); - } catch (NumberFormatException e) { - // SQUASH - return; - } - if ((imageWidth < 1) - || (imageWidth > 10000) - || (imageHeight < 1) - || (imageHeight > 10000) - ) { - return; - } - if (ps.equals("1")) { - scroll = true; - } else if (ps.equals("0")) { - scroll = false; - } else { - return; - } - - byte [] bytes = StringUtils.fromBase64(data.getBytes()); - if (bytes.length != (imageWidth * imageHeight * 3)) { - return; - } - - BufferedImage image = new BufferedImage(imageWidth, imageHeight, - BufferedImage.TYPE_INT_ARGB); - - for (int x = 0; x < imageWidth; x++) { - for (int y = 0; y < imageHeight; y++) { - int red = bytes[(y * imageWidth * 3) + (x * 3) ]; - if (red < 0) { - red += 256; - } - int green = bytes[(y * imageWidth * 3) + (x * 3) + 1]; - if (green < 0) { - green += 256; - } - int blue = bytes[(y * imageWidth * 3) + (x * 3) + 2]; - if (blue < 0) { - blue += 256; - } - int rgb = 0xFF000000 | (red << 16) | (green << 8) | blue; - image.setRGB(x, y, rgb); - } - } - - imageToCells(image, scroll); - } - - /** - * Parse a "Jexer" PNG or JPG image string into a bitmap image, and - * overlay that image onto the text cells. - * - * @param type 1 for PNG, 2 for JPG - * @param ps scroll token - * @param data pixel data - */ - private void parseJexerImageFile(final int type, final String ps, - final String data) { - - int imageWidth = 0; - int imageHeight = 0; - boolean scroll = false; - BufferedImage image = null; - try { - byte [] bytes = StringUtils.fromBase64(data.getBytes()); - - switch (type) { - case 1: - if ((bytes[0] != (byte) 0x89) - || (bytes[1] != 'P') - || (bytes[2] != 'N') - || (bytes[3] != 'G') - || (bytes[4] != (byte) 0x0D) - || (bytes[5] != (byte) 0x0A) - || (bytes[6] != (byte) 0x1A) - || (bytes[7] != (byte) 0x0A) - ) { - // File does not have PNG header, bail out. - return; - } - break; - - case 2: - if ((bytes[0] != (byte) 0XFF) - || (bytes[1] != (byte) 0xD8) - || (bytes[2] != (byte) 0xFF) - ) { - // File does not have JPG header, bail out. - return; - } - break; - - default: - // Unsupported type, bail out. - return; - } - - image = ImageIO.read(new ByteArrayInputStream(bytes)); - } catch (IOException e) { - // SQUASH - return; - } - assert (image != null); - imageWidth = image.getWidth(); - imageHeight = image.getHeight(); - if ((imageWidth < 1) - || (imageWidth > 10000) - || (imageHeight < 1) - || (imageHeight > 10000) - ) { - return; - } - if (ps.equals("1")) { - scroll = true; - } else if (ps.equals("0")) { - scroll = false; - } else { - return; - } - - imageToCells(image, scroll); - } - - /** - * Break up an image into the cells at the current cursor. - * - * @param image the image to display - * @param scroll if true, scroll the image and move the cursor - */ - private void imageToCells(final BufferedImage image, final boolean scroll) { - assert (image != null); - - /* - * Procedure: - * - * Break up the image into text cell sized pieces as a new array of - * Cells. - * - * Note original column position x0. - * - * For each cell: - * - * 1. Advance (printCharacter(' ')) for horizontal increment, or - * index (linefeed() + cursorPosition(y, x0)) for vertical - * increment. - * - * 2. Set (x, y) cell image data. - * - * 3. For the right and bottom edges: - * - * a. Render the text to pixels using Terminus font. - * - * b. Blit the image on top of the text, using alpha channel. - */ - int cellColumns = image.getWidth() / textWidth; - if (cellColumns * textWidth < image.getWidth()) { - cellColumns++; - } - int cellRows = image.getHeight() / textHeight; - if (cellRows * textHeight < image.getHeight()) { - cellRows++; - } - - // Break the image up into an array of cells. - Cell [][] cells = new Cell[cellColumns][cellRows]; - - for (int x = 0; x < cellColumns; x++) { - for (int y = 0; y < cellRows; y++) { - - int width = textWidth; - if ((x + 1) * textWidth > image.getWidth()) { - width = image.getWidth() - (x * textWidth); - } - int height = textHeight; - if ((y + 1) * textHeight > image.getHeight()) { - height = image.getHeight() - (y * textHeight); - } - - Cell cell = new Cell(); - cell.setImage(image.getSubimage(x * textWidth, - y * textHeight, width, height)); - - cells[x][y] = cell; - } - } - - int x0 = currentState.cursorX; - int y0 = currentState.cursorY; - for (int y = 0; y < cellRows; y++) { - for (int x = 0; x < cellColumns; x++) { - assert (currentState.cursorX <= rightMargin); - - // A real sixel terminal would render the text of the current - // cell first, then image over it (accounting for blank - // pixels). We do not support that. A cell is either text, - // or image, but not a mix of image-over-text. - DisplayLine line = display.get(currentState.cursorY); - line.replace(currentState.cursorX, cells[x][y]); - - // If at the end of the visible screen, stop. - if (currentState.cursorX == rightMargin) { - break; - } - // Room for more image on the visible screen. - currentState.cursorX++; - } - if (currentState.cursorY < scrollRegionBottom - 1) { - // Not at the bottom, down a line. - linefeed(); - } else if (scroll == true) { - // At the bottom, scroll as needed. - linefeed(); - } else { - // At the bottom, no more scrolling, done. - break; - } - - cursorPosition(currentState.cursorY, x0); - } - - if (scroll == false) { - cursorPosition(y0, x0); - } - - } - -}