/** * This file is part of "BBSSH" © 2010 Marc A. Paradise. BBSSH is based upon MidpSSH by Karl von Randow MidpSSH was * based upon Telnet Floyd and FloydSSH by Radek Polak. This file Copyright © 2010-2011 Marc A. Paradise, derived * from VT320.java © Matthias L. Jugel, Marcus Meissner 1996-2011. All Rights Reserved. Please visit * http://javatelnet.org/ for updates and contact. --LICENSE NOTICE-- This program is free software; you can * redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a * copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. --LICENSE NOTICE-- */ package org.bbssh.terminal; import java.io.IOException; import org.bbssh.util.Logger; /** * Implementation of a VT terminal emulation plus ANSI compatible. * <P> * * @author Matthias L. Jugel, Marcus Mei�ner, Marc A. Paradise */ public abstract class VT320 { /* Virtual key codes. */ public static final int VK_ENTER = '\n'; public static final int VK_BACK_SPACE = '\b'; public static final char VK_TAB = '\t'; public static final int VK_CANCEL = 0x03; public static final int VK_CLEAR = 0x0C; public static final int VK_SHIFT = 0x10; public static final int VK_CONTROL = 0x11; public static final int VK_ALT = 0x12; public static final int VK_PAUSE = 0x13; public static final int VK_CAPS_LOCK = 0x14; public static final int VK_ESCAPE = 0x1B; public static final int VK_SPACE = 0x20; public static final int VK_PAGE_UP = 0x21; public static final int VK_PAGE_DOWN = 0x22; public static final int VK_END = 0x23; public static final int VK_HOME = 0x24; /** * Constant for the non-numpad <b>left </b> arrow key. */ public static final int VK_LEFT = 0x25; /** * Constant for the non-numpad <b>up </b> arrow key. */ public static final int VK_UP = 0x26; /** * Constant for the non-numpad <b>right </b> arrow key. */ public static final int VK_RIGHT = 0x27; /** * Constant for the non-numpad <b>down </b> arrow key. */ public static final int VK_DOWN = 0x28; public static final int VK_COMMA = 0x2C; /** * Constant for the "-" key. */ public static final int VK_MINUS = 0x2D; public static final int VK_PERIOD = 0x2E; public static final int VK_SLASH = 0x2F; /** VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */ public static final int VK_0 = 0x30; public static final int VK_1 = 0x31; public static final int VK_2 = 0x32; public static final int VK_3 = 0x33; public static final int VK_4 = 0x34; public static final int VK_5 = 0x35; public static final int VK_6 = 0x36; public static final int VK_7 = 0x37; public static final int VK_8 = 0x38; public static final int VK_9 = 0x39; public static final int VK_SEMICOLON = 0x3B; public static final int VK_EQUALS = 0x3D; /** VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */ public static final int VK_A = 0x41; public static final int VK_B = 0x42; public static final int VK_C = 0x43; public static final int VK_D = 0x44; public static final int VK_E = 0x45; public static final int VK_F = 0x46; public static final int VK_G = 0x47; public static final int VK_H = 0x48; public static final int VK_I = 0x49; public static final int VK_J = 0x4A; public static final int VK_K = 0x4B; public static final int VK_L = 0x4C; public static final int VK_M = 0x4D; public static final int VK_N = 0x4E; public static final int VK_O = 0x4F; public static final int VK_P = 0x50; public static final int VK_Q = 0x51; public static final int VK_R = 0x52; public static final int VK_S = 0x53; public static final int VK_T = 0x54; public static final int VK_U = 0x55; public static final int VK_V = 0x56; public static final int VK_W = 0x57; public static final int VK_X = 0x58; public static final int VK_Y = 0x59; public static final int VK_Z = 0x5A; public static final int VK_OPEN_BRACKET = 0x5B; public static final int VK_BACK_SLASH = 0x5C; public static final int VK_CLOSE_BRACKET = 0x5D; public static final int VK_NUMPAD0 = 0x60; public static final int VK_NUMPAD1 = 0x61; public static final int VK_NUMPAD2 = 0x62; public static final int VK_NUMPAD3 = 0x63; public static final int VK_NUMPAD4 = 0x64; public static final int VK_NUMPAD5 = 0x65; public static final int VK_NUMPAD6 = 0x66; public static final int VK_NUMPAD7 = 0x67; public static final int VK_NUMPAD8 = 0x68; public static final int VK_NUMPAD9 = 0x69; public static final int VK_MULTIPLY = 0x6A; public static final int VK_ADD = 0x6B; /** * Constant for the Numpad Separator key. */ public static final int VK_SEPARATOR = 0x6C; public static final int VK_SUBTRACT = 0x6D; public static final int VK_DECIMAL = 0x6E; public static final int VK_DIVIDE = 0x6F; public static final int VK_DELETE = 0x7F; /* ASCII DEL */ public static final int VK_NUM_LOCK = 0x90; public static final int VK_SCROLL_LOCK = 0x91; /** Constant for the F1 function key. */ public static final int VK_F1 = 0x70; /** Constant for the F2 function key. */ public static final int VK_F2 = 0x71; /** Constant for the F3 function key. */ public static final int VK_F3 = 0x72; /** Constant for the F4 function key. */ public static final int VK_F4 = 0x73; /** Constant for the F5 function key. */ public static final int VK_F5 = 0x74; /** Constant for the F6 function key. */ public static final int VK_F6 = 0x75; /** Constant for the F7 function key. */ public static final int VK_F7 = 0x76; /** Constant for the F8 function key. */ public static final int VK_F8 = 0x77; /** Constant for the F9 function key. */ public static final int VK_F9 = 0x78; /** Constant for the F10 function key. */ public static final int VK_F10 = 0x79; /** Constant for the F11 function key. */ public static final int VK_F11 = 0x7A; /** Constant for the F12 function key. */ public static final int VK_F12 = 0x7B; public static final int VK_INSERT = 0x9B; public final static int KEY_CONTROL = 0x00010000; public final static int KEY_SHIFT = 0x00020000; public final static int KEY_ALT = 0x00040000; public final static int KEY_ACTION = 0x00080000; private String terminalID = "vt320"; // Current cursor marker. public int ROW, COLUMN; int attributes = 0; int Sc, Sr, Sa, Stm, Sbm; char Sgr, Sgl; char Sgx[]; int insertmode = 0; int statusmode = 0; boolean vt52mode = false; // false - numeric, true - application boolean keypadmode = false; boolean output8bit = false; int normalcursor = 0; boolean moveoutsidemargins = true; boolean wraparound = true; boolean sendcrlf = true; boolean capslock = false; boolean numlock = false; int mouserpt = 0; byte mousebut = 0; int lastwaslf = 0; public int numScrollbackLines = 0; boolean usedcharsets = false; // Reference: http://en.wikipedia.org/wiki/C0_and_C1_control_codes /** * The ESC key on the keyboard will cause this character to be sent on most * systems. It can be used in software user interfaces to exit from a * screen, menu, or mode, or in device-control protocols (e.g., printers and * terminals) to signal that what follows is a special command sequence * rather than normal text. In systems based on ISO/IEC 2022, even if * another set of C0 control codes are used, this octet is required to * always represent the escape character. */ private final static char ESC = 27; /** * Move the active position one line down, to eliminate ambiguity about the * meaning of LF. Deprecated in 1988 and withdrawn in 1992 from ISO/IEC 6429 * (1986 and 1991 respectively for ECMA-48). */ private final static char IND = 132; /** * Equivalent to CR+LF. Used to mark end-of-line on some IBM mainframes. */ private final static char NEL = 133; /** * reverse line feed */ private final static char RI = 141; /** * Next character invokes a graphic character from the G2 graphic set. In * systems that conform to ISO/IEC 4873 (ECMA-43), even if a C1 set other * than the default is used, these two octets may only be used for this * purpose. */ private final static char SS2 = 142; /** * Next character invokes a graphic character from the G3 graphic set. In * systems that conform to ISO/IEC 4873 (ECMA-43), even if a C1 set other * than the default is used, these two octets may only be used for this * purpose. */ private final static char SS3 = 143; /** * Followed by a string of printable characters (0x20 through 0x7E) and * format effectors (0x08 through 0x0D), terminated by ST (0x9C). */ private final static char DCS = 144; /** * Causes a character tabulation stop to be set at the active position. */ private final static char HTS = 136; /** * Used to introduce control sequences that take parameters. */ private final static char CSI = 155; /** * Operating System Command - Followed by a string of printable characters * (0x20 through 0x7E) and format effectors (0x08 through 0x0D), terminated * by ST (0x9C). This along with PM and APC were intended for use to allow * in-band signaling of protocol information, but are rarely used for that * purpose. * * @see VT320.OSC * @see VT320.PM */ protected final static char OSC = 157; /** * Privacy message * * @see VT320.OSC */ protected final static char PM = 158; /** * Application Program Command * * @see VT320.OSC */ protected final static char APC = 159; // These are all control code that must be honored EVEN if tehy arrive in // the middle of an // escape sequence - so we check for them first. protected final static char BEL = 007;// bell protected final static char BS = 0x08; // backspace protected final static char HT = 0x09; // tab protected final static char LF = 0x0A; // line feed protected final static char VT = 0x0B; // line feed protected final static char FF = 0x0C; // line feed protected final static char CR = 0x0D;// gives a carriage return; protected final static char SO = 0x0E;// activates the G1 character set, and // if LF/NL (new line mode) is set // also a carriage return; protected final static char SI = 0x0F;// activates the G0 character set protected final static char CAN = 0x18; // interrupt escape sequence protected final static char SUB = 0x1A; // interrupt escape sequence // protected final static char ESC = 0x1B; // start an escape sequence (and // interrupt escape sequence) protected final static char DEL = 0x7F; // ignored // protected final static char CSI = 0x9B; // CSI - ESC [ private final static int TSTATE_DATA = 0; // ESC - followed by nothing. not documented in ansi standard... private final static int TSTATE_ESC = 1; // ESC [ // Control Sequence Intro private final static int TSTATE_CSI = 2; // ESC P DCS Device Control String private final static int TSTATE_DCS = 3; // ESC [? -- eh? Not documented in vt100 - later? private final static int TSTATE_DCEQ = 4; // ESC # private final static int TSTATE_ESCSQUARE = 5; // ESC ] private final static int TSTATE_OSC = 6; // ESC (? private final static int TSTATE_SETG0 = 7; // ESC )? * private final static int TSTATE_SETG1 = 8; // ESC *? private final static int TSTATE_SETG2 = 9; // ESC +? private final static int TSTATE_SETG3 = 10; // ESC [ Pn $ private final static int TSTATE_CSI_DOLLAR = 11; // ESC [ ! private final static int TSTATE_CSI_EX = 12; // ESC <space> private final static int TSTATE_ESCSPACE = 13; private final static int TSTATE_VT52X = 14; private final static int TSTATE_VT52Y = 15; private final static int TSTATE_CSI_TICKS = 16; /* * The graphics charsets B - default ASCII A - ISO Latin 1 0 - DEC SPECIAL < * - User defined .... */ char gx[] = {// same initial set as in XTERM. 'B', // g0 '0', // g1 'B', // g2 'B', // g3 }; char gl = 0; // default GL to G0 char gr = 2; // default GR to G2 int onegl = -1; // single shift override for GL. private String FunctionKey[]; private String TabKey[]; private String KeyUp[], KeyDown[], KeyLeft[], KeyRight[]; private String Insert[]; private String KeyHome[], KeyEnd[], PrevScn[], NextScn[], Escape[], BackSpace[]; // , NUMPlus[]; // NUMDot[], private String osc, dcs; /* to memorize OSC & DCS control sequence */ /** vt320 state variable (internal) */ protected int term_state = TSTATE_DATA; /** Tabulators */ private byte[] Tabs; /** The list of integers as used by CSI */ private int[] DCEvars = new int[30]; private int DCEvar; private int writeBufferIndex = 0; private byte[] writeBuffer = new byte[256]; private byte[] stringConversionBuffer = new byte[256]; public long[][] terminalData; // Buffer sizes public int bufSize; public int maxBufSize; /** actual screen start */ public int screenBase; /** where the screen starts displaying - viewport? */ public int windowBase; // Scrolling margins - these indicate the scrolling region WITHIN the // screen, in terms of visible rows. scrolling can occur in a region // that is smaller than the visible screen. /** Top scroll margin */ private int topMargin; /** Bottom scroll margin */ private int bottomMargin; // cursor variable // indicates whether or not to show the cursor public boolean showcursor = true; // visual cursor current xpos public int cursorX; // visual cursor current xpos public int cursorY; /** Scroll up when inserting a line. */ public final static boolean SCROLL_UP = false; /** Scroll down when inserting a line. */ public final static boolean SCROLL_DOWN = true; // Note that our XATTR values are stored in the termData as part of the CHAR // section - char needs 16 bits, leaving // us 16 for extended values. /** * Found only int he first column of a row, this attribute menas that the * row needs to be redrawn. */ /** If found in position 0 of theline, indicates that the line is dirty. */ public final static long XATTR_DIRTY = 0x00010000; /** An automatic wrap occurs at this character */ public final static long XATTR_WRAP = 0x00020000; /** Character is selected */ public static final int XATTR_SELECTED = 0x10000000; /** Make character normal. */ public final static int NORMAL = 0x00; /** Make character bold. */ public final static int BOLD = 0x01; /** Underline character. */ public final static int UNDERLINE = 0x02; /** Invert character. */ public final static int INVERT = 0x04; /** Lower intensity character. */ public final static int LOW = 0x08; /** Character has color **/ public final static int COLOR = 0xff0; /** FG color indicator **/ public final static int COLOR_FG = 0x0f0; public final static int COLOR_BG = 0xf00; private Object termBufferMutex = new Object(); public String debugName; public final static int BLINK = 0x1000; public static final byte FK_VT100 = 0; public static final byte FK_LINUX_APP_KEYPAD = 1; public static final byte FK_LINUX = 2; // @todo can we replace dual arrays with Character[rows][cols] // (attribute,value)? /** * Create a new vt320 terminal and intialize it with useful settings. * * @param width * terminal width * @param height * terminal height */ public VT320(int width, int height) { debugName = ""; setScreenSize(width, height, false); setBufferSize(height); resetTabs(); Insert = new String[4]; KeyHome = new String[4]; KeyEnd = new String[4]; NextScn = new String[4]; PrevScn = new String[4]; Escape = new String[4]; BackSpace = new String[4]; TabKey = new String[4]; // @note - Array of objects for 80x24 // 1920 * (4 + 2 + 4 + 4) = 26880 + 12 for array. // obj reference + char value + attributes + more attributes // Current setup = 1920 * ( // @todo - if these modes are always equal, do we really need to // keep these paralell arrays? Insert[0] = Insert[1] = Insert[2] = Insert[3] = "\u001b[2~"; KeyHome[0] = KeyHome[1] = KeyHome[2] = KeyHome[3] = "\u001b[H"; KeyEnd[0] = KeyEnd[1] = KeyEnd[2] = KeyEnd[3] = "\u001b[F"; PrevScn[0] = PrevScn[1] = PrevScn[2] = PrevScn[3] = "\u001b[5~"; NextScn[0] = NextScn[1] = NextScn[2] = NextScn[3] = "\u001b[6~"; Escape[0] = Escape[1] = Escape[2] = Escape[3] = "\u001b"; BackSpace[0] = BackSpace[1] = BackSpace[2] = BackSpace[3] = "\b"; FunctionKey = new String[21]; FunctionKey[0] = ""; /* * Note that this is VT100 compliant - see enableFunctionKeyWorkaround * for option compliant with "linux", "ansi", and several others. */ FunctionKey[1] = "\u001bOP"; FunctionKey[2] = "\u001bOQ"; FunctionKey[3] = "\u001bOR"; FunctionKey[4] = "\u001bOS"; /* following are defined differently for vt220 / vt132 ... */ FunctionKey[5] = "\u001b[15~"; FunctionKey[6] = "\u001b[17~"; FunctionKey[7] = "\u001b[18~"; FunctionKey[8] = "\u001b[19~"; FunctionKey[9] = "\u001b[20~"; FunctionKey[10] = "\u001b[21~"; FunctionKey[11] = "\u001b[23~"; FunctionKey[12] = "\u001b[24~"; // @todo - support and alternative modes for F13-F20, as per here; and // handling for shift keys: // http://aperiodic.net/phil/archives/Geekery/term-function-keys.html TabKey[0] = "\u0009"; TabKey[1] = "\u001bOP\u0009"; TabKey[2] = TabKey[3] = ""; KeyUp = new String[4]; KeyUp[0] = "\u001b[A"; KeyDown = new String[4]; KeyDown[0] = "\u001b[B"; KeyRight = new String[4]; KeyRight[0] = "\u001b[C"; KeyLeft = new String[4]; KeyLeft[0] = "\u001b[D"; } private void resetTabs() { int nw = width; if (nw < 132) { nw = 132; // catch possible later 132/80 resizes } Tabs = new byte[nw]; for (int i = 0; i < nw; i += 8) { Tabs[i] = 1; } } /** * Create a default vt320 terminal with 80 columns and 24 lines. */ public VT320() { this(80, 24); } public VT320(String name) { this(); this.debugName = name; } /** * Send data to remote host * * @param b * the array of bytes to be sent * @param offset * offset from start of array to write * @param length * number bytes to write */ protected void write(byte[] b, int offset, int length) { // Logger.debug("VT320.write(b, o, l) begin"); if (writeBufferIndex + length > writeBuffer.length) { // Logger.debug("VT320.write recursing - write length too large"); if (writeBufferIndex > 0) { // First write out what we have and then try to add this to the // buffer flush(); write(b, offset, length); } else { // Buffer is too small so write out in parts System.arraycopy(b, offset, writeBuffer, 0, writeBuffer.length); writeBufferIndex = writeBuffer.length; flush(); write(b, offset + writeBuffer.length, length - writeBuffer.length); } // Logger.debug("VT320.write recursing - outcall"); } else { // Logger.debug("VT320.write non-recursive begin"); System.arraycopy(b, offset, writeBuffer, writeBufferIndex, length); // Logger.debug("VT320.write non-recursive complete"); writeBufferIndex += length; } // Logger.debug("VT320.write(b, o, l) end"); } protected void flush() { // Logger.debug("VT320.flush invoked - idx = " + writeBufferIndex); try { sendData(writeBuffer, 0, writeBufferIndex); } catch (IOException e) { Logger.error("VT320.flush: IOException received - " + e.getMessage()); } writeBufferIndex = 0; // Logger.debug("VT320.flush complete."); } public abstract void sendData(byte[] b, int offset, int length) throws IOException; /** * Play the beep sound ... */ public abstract void beep(); /** * Put string at current cursor position. Moves cursor according to the * String. Does NOT wrap. * * @param s * the string */ public void putString(String s) { int len = s.length(); if (len > 0) { synchronized (termBufferMutex) { setLineDirty(ROW - 1); for (int i = 0; i < len; i++) { putChar(s.charAt(i), false); } // Move to end of page for cursor. setCursorPosition(COLUMN, ROW); } } } public void putStringStartLine(String s) { int len = s.length(); if (len > 0) { synchronized (termBufferMutex) { setCursorPosition(0, ROW); setLineDirty(ROW - 1, 1); for (int i = 0; i < len; i++) { putChar(s.charAt(i), false); } setCursorPosition(0, ROW); } } } /** we should do localecho (passed from other modules). false is default */ public boolean localecho = false; public long handledCharCount = 0; /** * Enable or disable the local echo property of the terminal. * * @param echo * true if the terminal should echo locally */ public void setLocalEcho(boolean echo) { localecho = echo; } /** * Set the terminal id used to identify this terminal. * * @param terminalID * the id string */ public void setTerminalID(String terminalID) { this.terminalID = terminalID; } // // public void setAnswerBack( String ab ) { // // this.answerBack = unEscape( ab ); // // } /** * Get the terminal id used to identify this terminal. */ public String getTerminalID() { return terminalID; } /** * A small conveniance method that converts the string to a byte array for * sending. * * @param s * the string to be sent */ private boolean write(String s, boolean doecho) { // Logger.debug("VT320.write(s,b) begin"); if (s == null) // aka the empty string. { // Logger.debug("VT320.write(s,b) end empty string"); return true; } // @todo UTF8 UTF-8 ? /* * NOTE: getBytes() honours some locale, it *CONVERTS* the string. * However, we output only 7bit stuff towards the target, and *some* 8 * bit control codes. We must not mess up the latter, so we do hand by * hand copy. */ // Maybe extend writeBuffer if (stringConversionBuffer.length < s.length()) { stringConversionBuffer = new byte[s.length()]; } // Fill writeBuffer for (int i = 0; i < s.length(); i++) { stringConversionBuffer[i] = (byte) s.charAt(i); } write(stringConversionBuffer, 0, s.length()); if (doecho) { putString(s); } // Logger.debug("VT320.write(s,b) end"); return true; } private boolean write(String s) { return write(s, localecho); } /** * A small conveniance method thar converts a 7bit string to the 8bit * version depending on VT52/Output8Bit mode. * * @param s * the string to be sent */ private boolean writeSpecial(String s) { if (s == null) { return true; } if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == 'O'))) { if (vt52mode) { if ((s.charAt(2) >= 'P') && (s.charAt(2) <= 'S')) { s = "\u001b" + s.substring(2); /* ESC x */ } else { s = "\u001b?" + s.substring(2); /* ESC ? x */ } } else { if (output8bit) { s = "\u008f" + s.substring(2); /* SS3 x */ } /* else keep string as it is */ } } if (((s.length() >= 3) && (s.charAt(0) == 27) && (s.charAt(1) == '['))) { if (output8bit) { s = "\u009b" + s.substring(2); /* CSI ... */ } /* else keep */ } return write(s, false); } /** * Handle key Typed events for the terminal, this will get all normal key * types, but no shift/alt/control/numlock. */ public void keyTyped(int keyCode, char keyChar, int modifiers) { _keyTyped(keyCode, keyChar, modifiers); flush(); } // Provides appropriate handling for special keys if shift/alt/ctrl is // in use, but otherwise sends normally (?) // @todo look into terminal spec to see what really has to happen here and // why. public void dispatchKey(int keyCode, char keyChar, int modifiers) { if ((keyCode >= VK_PAGE_UP && keyCode <= VK_DOWN) || keyCode == VK_BACK_SPACE || keyCode == VK_INSERT || (keyCode >= VK_F1 && keyCode <= VK_F12)) { keyPressed(keyCode, modifiers); } else { keyTyped(keyCode, keyChar, modifiers); } } /** * Properly sends only the following (along with any modifiers) VK_F1 * through VK_F12 up/dn/left/right/backspace/esc/home/end/insert */ public void keyPressed(int keyCode, int modifiers) { boolean control = (modifiers & KEY_CONTROL) != 0; boolean shift = (modifiers & KEY_SHIFT) != 0; boolean alt = (modifiers & KEY_ALT) != 0; int xind; xind = 0; // @todo - key COMBINATIONS such as ctrl+shift+alt are not possible // using this. if (shift) { xind = 1; } if (control) { xind = 2; } if (alt) { if (altPrefixesMeta) { writeSpecial(Escape[0]); } else { xind = 3; } } // @todo ctrl/alt/shift FN key is not working if (keyCode >= VT320.VK_F1 && keyCode <= VT320.VK_F12) { writeSpecial(FunctionKey[keyCode - VT320.VK_F1 + 1]); } else { // @todo this could be done with a map look up instead of // conditional/branching code. switch (keyCode) { case VT320.VK_UP: writeSpecial(KeyUp[xind]); break; case VT320.VK_DOWN: writeSpecial(KeyDown[xind]); break; case VT320.VK_LEFT: writeSpecial(KeyLeft[xind]); break; case VT320.VK_RIGHT: writeSpecial(KeyRight[xind]); break; case VT320.VK_PAGE_DOWN: writeSpecial(NextScn[xind]); break; case VT320.VK_PAGE_UP: writeSpecial(PrevScn[xind]); break; case VT320.VK_INSERT: writeSpecial(Insert[xind]); break; case VT320.VK_BACK_SPACE: writeSpecial(BackSpace[xind]); if (localecho) { if (BackSpace[xind].equals("\b")) { putString("\b \b"); // make the last char 'deleted' } else { putString(BackSpace[xind]); // echo it } } break; case VT320.VK_HOME: writeSpecial(KeyHome[xind]); break; case VT320.VK_END: writeSpecial(KeyEnd[xind]); break; } } flush(); } public void stringTyped(String str) { // Logger.debug("VT320.stringTyped " + str); for (int i = 0; i < str.length(); i++) { _keyTyped(0, str.charAt(i), 0); } flush(); } // @todo we need to present a common interface for sending character -- // there's no clear distinction for the end user about when we should send // keyTyped vs keyPressed. private void _keyTyped(int keyCode, char keyChar, int modifiers) { // Logger.debug("VT320._keyTyped: " + (int) keyChar); @todo DebugVT320 boolean control = (modifiers & KEY_CONTROL) != 0; boolean shift = (modifiers & KEY_SHIFT) != 0; boolean alt = (modifiers & KEY_ALT) != 0; if (keyChar == '\t') { if (shift) { write(TabKey[1], false); } else { if (control) { write(TabKey[2], false); } else { if (alt) { if (altPrefixesMeta) { writeSpecial(Escape[0]); write(TabKey[0], false); } else { write(TabKey[3], false); } } else { write(TabKey[0], false); } } } return; } if (alt && !altPrefixesMeta) { if (keyChar == VT320.VK_ESCAPE) { writeSpecial(Escape[3]); } else { write("" + ((char) (keyChar | 0x80))); } return; } if (((keyCode == VT320.VK_ENTER) || (keyChar == 10)) && !control) { write("\r", false); if (localecho) { putString("\r\n"); // bad hack } return; } int xind = 0; // @todo only one at a time, but what if we need to combine? if (shift) { xind = 1; } if (control) { xind = 2; } if (alt && altPrefixesMeta) { writeSpecial(Escape[0]); } if (keyCode == VT320.VK_ESCAPE) { writeSpecial(Escape[xind]); return; } // enter, tab, esc, backspace, if (!((keyChar == 8) || (keyChar == 127) || (keyChar == '\r') || (keyChar == '\n'))) { // @todo - map - hw much of this is needed? Shift keys are not, at // least. // KARL support for control codes and shift keys if (control) { if (keyChar >= 'a' && keyChar <= 'z') { keyChar = (char) (keyChar - 'a' + 'A'); } if (keyChar >= 'A' && keyChar <= 'Z') { keyChar = (char) (keyChar - 'A' + 1); write("" + keyChar); } else { switch (keyChar) { case '@': keyChar = (char) 0; break; case '[': keyChar = (char) 27; break; case '\\': keyChar = (char) 28; break; case ']': keyChar = (char) 29; break; case '^': keyChar = (char) 30; break; case '_': keyChar = (char) 31; break; } write("" + keyChar); } } else if (shift) { if (keyChar >= 'a' && keyChar <= 'z') { keyChar = (char) (keyChar - 'a' + 'A'); } write("" + keyChar); } else { write("" + keyChar); } return; } } private void _SetCursor(int row, int col) { int maxr = height; int tm = getTopMargin(); ROW = (row < 0) ? 0 : row; COLUMN = (col < 0) ? 0 : col; if (!moveoutsidemargins) { ROW += tm; maxr = getBottomMargin(); } if (ROW > maxr) { ROW = maxr; } } // StringBuffer buf = new StringBuffer(1024); public void putChar(char c, boolean doshowcursor) { handledCharCount++; int rows = height; int columns = width; int tm = getTopMargin(); int bm = getBottomMargin(); setLineDirty(ROW, 1); // if (buf.length() >= 1000) { // Logger.error(buf.toString()); // buf = new StringBuffer(1024); // } else { // buf.append('<').append(c).append(" / ").append((int) c).append(">"); // } // These control sequences are required to take precedence - even if // another escape sequence is // in process. So this means in TSTATE_DATA, TSTATE_CSI, and TSTATE_ESC // we must pause and process. // Reference: // http://vt100.net/docs/vt100-ug/chapter3.html // "Terminal Control Commands" // Control characters (codes 08 to 378 inclusive) are specifically // excluded from the control sequence syntax, // but may be embedded within a control sequence. Embedded control // characters are executed as soon as they are // encountered by the VT100. The processing of the control sequence then // continues with the next character // received. The exceptions are: if the character ESC occurs, the // current control sequence is aborted, and a new // one commences beginning with the ESC just received. If the character // CAN (308) or the character SUB (328) // occurs, the current control sequence is aborted. The ability to embed // control characters allows the // synchronization characters XON and XOFF to be interpreted properly // without affecting the control sequence. // boolean done = false; if (term_state == TSTATE_DATA || term_state == TSTATE_CSI || term_state == TSTATE_ESC) { done = true; switch (c) { case SS3: onegl = 3; break; case SS2: onegl = 2; break; case SUB: case CAN: // interrupt escape sequnce setTermState(TSTATE_DATA); break; case DEL: // ignored break; case CSI: // should be in the 8bit section, but some BBS use this DCEvar = 0; DCEvars[0] = 0; DCEvars[1] = 0; DCEvars[2] = 0; DCEvars[3] = 0; setTermState(TSTATE_CSI); // also aborts previous mode break; case ESC: setTermState(TSTATE_ESC); // aborts previous mode lastwaslf = 0; break; case FF: // FormFeed, - implementation depednent - some BBSs use it // as Home, so we will too. deleteArea(0, 0, columns, rows, attributes); COLUMN = ROW = 0; break; case BS: /* 8 */ COLUMN--; if (COLUMN < 0) { COLUMN = 0; } lastwaslf = 0; break; case HT: // tabs are movement, non-destructive... do { COLUMN++; } while (COLUMN < columns && (Tabs[COLUMN] == 0)); // lastwaslf = 0; break; case LF: case VT: if (ROW == bm || ROW >= rows - 1) { insertLine(ROW, 1, SCROLL_UP); } else { ROW++; } break; case CR: COLUMN = 0; // putty: // CR: term->curs.x = 0; // term->wrapnext = FALSE; // ff: if compat, clear // vt, lf scroll and add line // if (lastwaslf != 0 && lastwaslf != c) { // break; // } // lastwaslf = c; break; case BEL: beep(); break; case SO: /* ^N, Shift out - Put G1 into GL */ gl = 1; usedcharsets = true; // @todo this is also supposed to add lf/nl if newline mode // enabled break; case SI: /* ^O, Shift in - Put G0 into GL */ // ondocumented as to whether this adds lf/nl like SO, so assume // not gl = 0; usedcharsets = true; break; default: done = false; break; } // switch c } // @todo Serious kludge for now -- will have to revisit. if (!done) { switch (term_state) { case TSTATE_DATA: /* * FIXME: we shouldn't use chars with bit 8 set if ibmcharset. * probably... but some BBS do anyway... */ boolean doneflag = true; switch (c) { // @todo are these 8 bit control codes also escape // sequence // interrupting? case OSC: osc = ""; setTermState(TSTATE_OSC); break; case RI: if (ROW > tm) { ROW--; } else { insertLine(ROW, 1, SCROLL_DOWN); } break; case IND: if (ROW == bm || ROW == rows - 1) { insertLine(ROW, 1, SCROLL_UP); } else { ROW++; } break; case NEL: if (ROW == bm || ROW == rows - 1) { insertLine(ROW, 1, SCROLL_UP); } else { ROW++; } COLUMN = 0; break; case HTS: Tabs[COLUMN] = 1; break; case DCS: dcs = ""; setTermState(TSTATE_DCS); break; default: doneflag = false; break; } if (doneflag) { break; } if (onegl >= 0) { onegl = -1; } lastwaslf = 0; if (c < 32) { if (c != 0) /* * break; some BBS really want those characters, * like hearst etc. */{ if (c == 0) /* print 0 ... you bet */{ break; } } } if (COLUMN >= columns) { if (wraparound) { // Mark the current line as wrapped if (ROW > -1 && ROW < height - 1) { terminalData[ROW][columns - 1] |= XATTR_WRAP; } if (ROW < rows - 1) { ROW++; } else { insertLine(ROW, 1, SCROLL_UP); } COLUMN = 0; } else { // cursor stays on last character. COLUMN = columns - 1; } } // Mapping if DEC Special is chosen charset /* if(true || (statusmode == 0)) { */ if (insertmode == 1) { insertChar(COLUMN, ROW, c, attributes); } else { putChar(COLUMN, ROW, c, attributes); } COLUMN++; break; // TSTATE_DATA case TSTATE_OSC: // @todo OSC is where we'll look to implement window title // changes if ((c < 0x20) && (c != ESC)) {// NP - No printing // character handle_osc( osc ); setTermState(TSTATE_DATA); break; } // but check for vt102 ESC \ if (c == '\\' && osc.charAt(osc.length() - 1) == ESC) { // handle_osc( osc ); setTermState(TSTATE_DATA); break; } osc = osc + c; break; case TSTATE_ESCSPACE: setTermState(TSTATE_DATA); switch (c) { case 'F': /* * S7C1T, Disable output of 8-bit controls, use 7-bit */ output8bit = false; break; case 'G': /* S8C1T, Enable output of 8-bit control codes */ output8bit = true; break; default: } break; case TSTATE_ESC: setTermState(TSTATE_DATA); switch (c) { case ' ': setTermState(TSTATE_ESCSPACE); break; case '#': setTermState(TSTATE_ESCSQUARE); break; case 'c': resetTerminal(); break; case '[': DCEvar = 0; DCEvars[0] = 0; DCEvars[1] = 0; DCEvars[2] = 0; DCEvars[3] = 0; setTermState(TSTATE_CSI); break; case ']': osc = ""; setTermState(TSTATE_OSC); break; case 'P': dcs = ""; setTermState(TSTATE_DCS); break; case 'A': /* CUU */ ROW--; if (ROW < 0) { ROW = 0; } break; case 'B': /* CUD */ ROW++; if (ROW > rows - 1) { ROW = rows - 1; } break; case 'C': COLUMN++; if (COLUMN >= columns) { COLUMN = columns - 1; } break; case 'I': // RI insertLine(ROW, 1, SCROLL_DOWN); break; case 'E': /* NEL */ if (ROW == bm || ROW == rows - 1) { insertLine(ROW, 1, SCROLL_UP); } else { ROW++; } COLUMN = 0; break; case 'D': /* IND */ if (ROW == bm || ROW == rows - 1) { insertLine(ROW, 1, SCROLL_UP); } else { ROW++; } break; case 'J': /* erase to end of screen */ if (ROW < rows - 1) { deleteArea(0, ROW + 1, columns, rows - ROW - 1, attributes); } if (COLUMN < columns - 1) { deleteArea(COLUMN, ROW, columns - COLUMN, 1, attributes); } break; case 'K': if (COLUMN < columns - 1) { deleteArea(COLUMN, ROW, columns - COLUMN, 1, attributes); } break; case 'M': // RI if (ROW > bm) // outside scrolling region { break; } if (ROW > tm) { // just go up 1 line. ROW--; } else { // scroll down insertLine(ROW, 1, SCROLL_DOWN); } /* else do nothing ; */ break; case 'H': /* right border probably ... */ if (COLUMN >= columns) { COLUMN = columns - 1; } Tabs[COLUMN] = 1; break; case 'N': // SS2 onegl = 2; break; case 'O': // SS3 onegl = 3; break; case '=': /* application keypad */ keypadmode = true; break; case '<': /* vt52 mode off */ vt52mode = false; break; case '>': /* normal keypad */ keypadmode = false; break; case '7': /* save cursor, attributes, margins */ Sc = COLUMN; Sr = ROW; Sgl = gl; Sgr = gr; Sa = attributes; Sgx = new char[4]; for (int i = 0; i < 4; i++) { Sgx[i] = gx[i]; } Stm = getTopMargin(); Sbm = getBottomMargin(); break; case '8': /* restore cursor, attributes, margins */ COLUMN = Sc; ROW = Sr; gl = Sgl; gr = Sgr; for (int i = 0; i < 4; i++) { gx[i] = Sgx[i]; } setTopMargin(Stm); setBottomMargin(Sbm, false); attributes = Sa; break; case '(': /* Designate G0 Character set (ISO 2022) */ setTermState(TSTATE_SETG0); usedcharsets = true; break; case ')': /* Designate G1 character set (ISO 2022) */ setTermState(TSTATE_SETG1); usedcharsets = true; break; case '*': /* Designate G2 Character set (ISO 2022) */ setTermState(TSTATE_SETG2); usedcharsets = true; break; case '+': /* Designate G3 Character set (ISO 2022) */ setTermState(TSTATE_SETG3); usedcharsets = true; break; case '~': /* Locking Shift 1, right */ gr = 1; usedcharsets = true; break; case 'n': /* Locking Shift 2 */ gl = 2; usedcharsets = true; break; case '}': /* Locking Shift 2, right */ gr = 2; usedcharsets = true; break; case 'o': /* Locking Shift 3 */ gl = 3; usedcharsets = true; break; case '|': /* Locking Shift 3, right */ gr = 3; usedcharsets = true; break; case 'Y': /* vt52 cursor address mode , next chars are x,y */ setTermState(TSTATE_VT52Y); break; default: Logger.debug("Unknown ESC: " + c + " / " + (int) c); break; } break; case TSTATE_VT52X: COLUMN = c - 37; setTermState(TSTATE_VT52Y); break; case TSTATE_VT52Y: ROW = c - 37; setTermState(TSTATE_DATA); break; case TSTATE_SETG0: if (!(c != '0' && c != 'A' && c != 'B' && c != '<')) { gx[0] = c; } setTermState(TSTATE_DATA); break; case TSTATE_SETG1: if (!(c != '0' && c != 'A' && c != 'B' && c != '<')) { gx[1] = c; } setTermState(TSTATE_DATA); break; case TSTATE_SETG2: if (!(c != '0' && c != 'A' && c != 'B' && c != '<')) { gx[2] = c; } setTermState(TSTATE_DATA); break; case TSTATE_SETG3: if (!(c != '0' && c != 'A' && c != 'B' && c != '<')) { gx[3] = c; } setTermState(TSTATE_DATA); break; case TSTATE_ESCSQUARE: switch (c) { case '8': for (int i = 0; i < columns; i++) { for (int j = 0; j < rows; j++) { putChar(i, j, 'E', 0); } } break; default: break; } setTermState(TSTATE_DATA); break; case TSTATE_DCS: if (c == '\\' && dcs.charAt(dcs.length() - 1) == ESC) { // handle_dcs( dcs ); setTermState(TSTATE_DATA); break; } dcs = dcs + c; break; case TSTATE_DCEQ: setTermState(TSTATE_DATA); switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + ((int) c) - 48; setTermState(TSTATE_DCEQ); break; case ';': DCEvar++; DCEvars[DCEvar] = 0; setTermState(TSTATE_DCEQ); break; case 's': // XTERM_SAVE missing! break; case 'r': // XTERM_RESTORE /* DEC Mode restore - I believe we should be toggling here? */ for (int i = 0; i <= DCEvar; i++) { switch (DCEvars[i]) { case 3: /* 80 columns */ setScreenSize(80, height, false); break; case 4: /* scrolling mode, smooth */ break; case 5: /* light background */ break; case 6: /* * DECOM (Origin Mode) move inside margins. */ moveoutsidemargins = true; break; case 7: /* DECAWM: Autowrap Mode */ wraparound = false; break; case 12:/* local echo off */ break; case 9: /* X10 mouse */ case 1000: /* xterm style mouse report on */ case 1001: case 1002: case 1003: mouserpt = DCEvars[i]; break; default: } } break; case 'h': // DECSET /* DEC Mode set */ for (int i = 0; i <= DCEvar; i++) { switch (DCEvars[i]) { case 1: /* Application cursor keys */ KeyUp[0] = "\u001bOA"; KeyDown[0] = "\u001bOB"; KeyRight[0] = "\u001bOC"; KeyLeft[0] = "\u001bOD"; break; case 2: /* DECANM */ vt52mode = false; break; case 3: /* 132 columns */ setScreenSize(132, height, false); break; case 6: /* DECOM: move inside margins. */ moveoutsidemargins = false; break; case 7: /* DECAWM: Autowrap Mode */ wraparound = true; break; case 25: /* turn cursor on */ showCursor(true); break; case 9: /* X10 mouse */ case 1000: /* xterm style mouse report on */ case 1001: case 1002: case 1003: mouserpt = DCEvars[i]; break; /* unimplemented stuff, fall through */ /* 4 - scrolling mode, smooth */ /* 5 - light background */ /* 12 - local echo off */ /* * 18 - DECPFF - Printer Form Feed Mode -> On */ /* * 19 - DECPEX - Printer Extent Mode -> Screen */ default: break; } } break; case 'i': // DEC Printer Control, autoprint, echo // screenchars to // printer // This is different to CSI i! // Also: "Autoprint prints a final display line only // when the // cursor is moved off the line by an autowrap or // LF, // FF, or // VT (otherwise do not print the line)." switch (DCEvars[0]) { case 1: break; case 4: break; case 5: break; } break; case 'l': // DECRST /* DEC Mode reset */ for (int i = 0; i <= DCEvar; i++) { switch (DCEvars[i]) { case 1: /* Application cursor keys */ KeyUp[0] = "\u001b[A"; KeyDown[0] = "\u001b[B"; KeyRight[0] = "\u001b[C"; KeyLeft[0] = "\u001b[D"; break; case 2: /* DECANM */ vt52mode = true; break; case 3: /* 80 columns */ setScreenSize(80, height, false); break; case 6: /* DECOM: move outside margins. */ moveoutsidemargins = true; break; case 7: /* DECAWM: Autowrap Mode OFF */ wraparound = false; break; case 25: /* turn cursor off */ showCursor(false); break; /* * ESC [ 1 l Keyboard action KAM Unlocked ESC [ 2 l * Insertion-replacement IRM Replace ESC [ 4 l * Send-receive SRM On ESC [ 1 2 l Linefeed/new line LMN * Linefeed ESC [ 2 0 l Cursor key DECCKM Cursor ESC [ ? * 1 l ANSI/VT52 DECANM VT52 ESC [ ? 2 l Scrolling * DECSCLM Jump ESC [ ? 4 l Screen DECSCNM Normal ESC [ * ? 5 l Origin DECOM Absolute ESC [ ? 6 l Auto wrap * DECAWM Off ESC [ ? 7 l Auto repeat DECARM Off ESC [ ? * 8 l Print form feed DECPFF Off ESC [ ? 1 8 l Print * extent DECPEX Scrolling region ESC [ ? 1 9 l /* 4 - * scrolling mode, jump */ /* * 12 - local echo on */ case 9: /* X10 mouse */ case 1000: /* xterm style mouse report OFF */ case 1001: case 1002: case 1003: mouserpt = 0; break; default: break; } } break; case 'n': switch (DCEvars[0]) { case 15: /* printer? no printer. */ write(((char) ESC) + "[?13n", false); flush(); break; default: break; } break; default: Logger.debug("Unknown TSTATE_DCEQ value: " + c + " / " + (int) c); break; } break; case TSTATE_CSI_EX: setTermState(TSTATE_DATA); switch (c) { case ESC: setTermState(TSTATE_ESC); break; default: break; } break; case TSTATE_CSI_TICKS: setTermState(TSTATE_DATA); switch (c) { case 'p': if (DCEvars[0] == 61) { output8bit = false; break; } if (DCEvars[1] == 1) { output8bit = false; } else { output8bit = true; /* 0 or 2 */ } break; default: Logger.debug("Unknown TSTATE_CSI_TICKS: " + c + " / " + (int) c); break; } break; case TSTATE_CSI_DOLLAR: setTermState(TSTATE_DATA); switch (c) { case '}': statusmode = DCEvars[0]; break; case '~': break; default: Logger.debug("Unknown TSTATE_CSI_DOLLAR: " + c + " / " + (int) c); break; } break; case TSTATE_CSI: setTermState(TSTATE_DATA); switch (c) { case '"': setTermState(TSTATE_CSI_TICKS); break; case '$': setTermState(TSTATE_CSI_DOLLAR); break; case '!': setTermState(TSTATE_CSI_EX); break; case '?': DCEvar = 0; DCEvars[0] = 0; setTermState(TSTATE_DCEQ); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': DCEvars[DCEvar] = DCEvars[DCEvar] * 10 + ((int) c) - 48; // buf.append("\r\nvar[" + DCEvar + "] = " + // DCEvars[DCEvar]); setTermState(TSTATE_CSI); break; case ';': DCEvar++; DCEvars[DCEvar] = 0; setTermState(TSTATE_CSI); break; // @note This can never be reached here - CSI + c is not a valid // combination // only CSI + ? + c (DA), or ESC + c (reset). Because '?' above // puts us in state DCEQ, // 'c' will only be reached in that state. // Leaving it here in case it was initially placed due to a // discovered // incompatibility/host incorrect behavior. case 'c':/* send primary device attributes */ replyDA(); break; case 'q': break; case 'g': /* used for tabsets */ switch (DCEvars[0]) { case 3:/* clear them */ Tabs = new byte[width]; break; case 0: Tabs[COLUMN] = 0; break; } break; case 'h': switch (DCEvars[0]) { case 4: insertmode = 1; break; case 20: sendcrlf = true; break; default: break; } break; case 'i': // Printer Controller mode. // "Transparent printing sends all output, except // the // CSI 4 i // termination string, to the printer and not the // screen, // uses an 8-bit channel if no parity so NUL and DEL // will be // seen by the printer and by the termination // recognizer // code, // and all translation and character set selections // are // bypassed." switch (DCEvars[0]) { case 0: break; case 4: break; case 5: break; default: } break; case 'l': switch (DCEvars[0]) { case 4: insertmode = 0; break; case 20: sendcrlf = false; break; default: break; } break; case 'A': // CUU { int limit; /* FIXME: xterm only cares about 0 and topmargin */ if (ROW > bm) { limit = bm + 1; } else if (ROW >= tm) { limit = tm; } else { limit = 0; } if (DCEvars[0] == 0) { ROW--; } else { ROW -= DCEvars[0]; } if (ROW < limit) { ROW = limit; } break; } case 'B': // CUD /* cursor down n (1) times */{ int limit; if (ROW < tm) { limit = tm - 1; } else if (ROW <= bm) { limit = bm; } else { limit = rows - 1; } if (DCEvars[0] == 0) { ROW++; } else { ROW += DCEvars[0]; } if (ROW > limit) { ROW = limit; } break; } case 'C': if (DCEvars[0] == 0) { COLUMN++; } else { COLUMN += DCEvars[0]; } if (COLUMN > columns - 1) { COLUMN = columns - 1; } break; case 'd': // CVA ROW = DCEvars[0]; break; case 'D': if (DCEvars[0] == 0) { COLUMN--; } else { COLUMN -= DCEvars[0]; } if (COLUMN < 0) { COLUMN = 0; } break; case 'r': // DECSTBM if (DCEvar > 0) // Ray: Any argument is optional { ROW = DCEvars[1] - 1; if (ROW < 0) { ROW = rows - 1; } else if (ROW >= rows) { ROW = rows - 1; } } else { ROW = rows - 1; } setBottomMargin(ROW, true); if (ROW >= DCEvars[0]) { ROW = DCEvars[0] - 1; if (ROW < 0) { ROW = 0; } } setTopMargin(ROW); _SetCursor(0, 0); // System.out.println("DECSTBM " + DCEvars[0] + " ; " + // DCEvars[1]); break; case 'G': /* Horizontal Position Absolute (CHA) */ case '`': // Same - SCO case '\'': // Same - SCO COLUMN = DCEvars[0] - 1; break; case 'H': /* CUP / cursor position */ /* gets 2 arguments */ _SetCursor(DCEvars[0] - 1, DCEvars[1] - 1); break; case 'f': /* move cursor 2 */ /* gets 2 arguments */ ROW = DCEvars[0] - 1; COLUMN = DCEvars[1] - 1; if (COLUMN < 0) { COLUMN = 0; } if (ROW < 0) { ROW = 0; } break; case 'S': /* ind aka 'scroll forward' */ if (DCEvars[0] == 0) { insertLine(rows - 1, SCROLL_UP); } else { insertLine(rows - 1, DCEvars[0], SCROLL_UP); } break; case 'L': /* insert n lines */ if (DCEvars[0] == 0) { insertLine(ROW, SCROLL_DOWN); } else { insertLine(ROW, DCEvars[0], SCROLL_DOWN); } break; case 'T': /* 'ri' aka scroll backward */ if (DCEvars[0] == 0) { insertLine(0, SCROLL_DOWN); } else { insertLine(0, DCEvars[0], SCROLL_DOWN); } break; case 'M': if (DCEvars[0] == 0) { deleteLine(ROW); } else { for (int i = 0; i < DCEvars[0]; i++) { deleteLine(ROW); } } break; case 'K': /* clear in line */ switch (DCEvars[0]) { case 6: /* * 97801 uses ESC[6K for delete to end of line */ case 0:/* clear to right */ if (COLUMN < columns - 1) { deleteArea(COLUMN, ROW, columns - COLUMN, 1, attributes); } break; case 1:/* clear to the left, including this */ if (COLUMN > 0) { deleteArea(0, ROW, COLUMN + 1, 1, attributes); } break; case 2:/* clear whole line */ deleteArea(0, ROW, columns, 1, attributes); break; } break; case 'J': /* clear below current line */ switch (DCEvars[0]) { case 0: if (ROW < rows - 1) { deleteArea(0, ROW + 1, columns, rows - ROW - 1, attributes); } if (COLUMN < columns - 1) { deleteArea(COLUMN, ROW, columns - COLUMN, 1, attributes); } break; case 1: if (ROW > 0) { deleteArea(0, 0, columns, ROW, attributes); } if (COLUMN > 0) { deleteArea(0, ROW, COLUMN + 1, 1, attributes);// include } // up // to // and including // current break; case 2: deleteArea(0, 0, columns, rows, attributes); break; } break; case '@': for (int i = 0; i < DCEvars[0]; i++) { insertChar(COLUMN, ROW, ' ', attributes); } break; case 'X': { int toerase = DCEvars[0]; if (toerase == 0) { toerase = 1; } if (toerase + COLUMN > columns) { toerase = columns - COLUMN; } deleteArea(COLUMN, ROW, toerase, 1, attributes); // does not change cursor position break; } case 'P': if (DCEvars[0] == 0) { DCEvars[0] = 1; } for (int i = 0; i < DCEvars[0]; i++) { deleteChar(COLUMN, ROW); } break; case 'n': switch (DCEvars[0]) { case 5: /* malfunction? No malfunction. */ writeSpecial(((char) ESC) + "[0n"); flush(); break; case 6: writeSpecial(((char) ESC) + "[" + ROW + ";" + COLUMN + "R"); flush(); break; default: break; } break; case 's': /* DECSC - save cursor */ Sc = COLUMN; Sr = ROW; Sa = attributes; break; case 'u': /* DECRC - restore cursor */ COLUMN = Sc; ROW = Sr; attributes = Sa; break; case 'm': /* attributes as color, bold , blink, */// SGR ESC [ nn // m if (DCEvar == 0 && DCEvars[0] == 0) { attributes = 0; } for (int i = 0; i <= DCEvar; i++) { switch (DCEvars[i]) { case 0: if (DCEvar > 0) { if (terminalID.equals("scoansi")) { attributes &= COLOR; /* * Keeps color. Strange * but true. */ } else { attributes = 0; } } break; case 1: attributes |= BOLD; attributes &= ~LOW; break; case 2: /* SCO color hack mode */ if (terminalID.equals("scoansi") && ((DCEvar - i) >= 2)) { int ncolor; attributes &= ~(COLOR | BOLD); ncolor = DCEvars[i + 1]; if ((ncolor & 8) == 8) { attributes |= BOLD; } ncolor = ((ncolor & 1) << 2) | (ncolor & 2) | ((ncolor & 4) >> 2); attributes |= ((ncolor) + 1) << 4; ncolor = DCEvars[i + 2]; ncolor = ((ncolor & 1) << 2) | (ncolor & 2) | ((ncolor & 4) >> 2); attributes |= ((ncolor) + 1) << 8; i += 2; } else { attributes |= LOW; } break; case 4: attributes |= UNDERLINE; break; case 7: attributes |= INVERT; break; case 5: /* blink on */ attributes |= BLINK; break; /* * 10 - ANSI X3.64-1979, select primary font, don't * display control chars, don't set bit 8 on output */ case 10: gl = 0; usedcharsets = true; break; /* * 11 - ANSI X3.64-1979, select second alt. font, * display control chars, set bit 8 on output */ case 11: /* SMACS , as */ case 12: gl = 1; usedcharsets = true; break; case 21: /* normal intensity */ attributes &= ~(LOW | BOLD); break; case 25: /* blinking off */ break; case 27: attributes &= ~INVERT; break; case 24: attributes &= ~UNDERLINE; break; case 22: attributes &= ~BOLD; break; case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: attributes &= ~COLOR_FG; attributes |= ((DCEvars[i] - 30) + 1) << 4; break; case 39: attributes &= ~COLOR_FG; break; case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: attributes &= ~COLOR_BG; attributes |= ((DCEvars[i] - 40) + 1) << 8; break; case 49: attributes &= ~COLOR_BG; break; default: break; } } break; default: Logger.debug("Unknown CSI: " + c + " / " + (int) c); break; } break; default: setTermState(TSTATE_DATA); break; } } if (COLUMN > columns) { COLUMN = columns; } if (ROW > rows) { ROW = rows; } if (COLUMN < 0) { COLUMN = 0; } if (ROW < 0) { ROW = 0; } if (doshowcursor) { setCursorPosition(COLUMN, ROW); } setLineDirty(ROW, 1); } private String daReply; private void replyDA() { // the only proper ways to ask are ESC [0c or ESC [c // so if we re ceived a value preceding c that wasn't 0, // it's not right. if (DCEvars[0] != 0) { Logger.warn("Received sequence ESC[?nc incorrectly, where n should be 0 and was " + DCEvars[0]); return; } // @todo make sure 1c is not a valid sequence? we don't haev a behavior // defined for it. // [?0c or [?c /* * reference http://computer-refuge.org/classiccmp/dec94mds/vt520rma.txt * "DA1" reference: http://vt100.net/docs/vt220-rm/chapter4.hs * "4.17.1.1" CSI ? 1; 2 c VT100 terminal ID DECTID CSI ? 1; 0 c VT101 * terminal ID DECTID CSI ? 6 c VT102 terminal ID DECTID CSI ? 62; 1; 2; * 7; 8 c VT220 North American DECTID CSI ? 62; 1; 2; 7; 8; 9 c VT220 * International DECTID CSI ? 63; 1; 2; 7; 8 c VT320 North American * DECTID CSI ? 63; 1; 2; 7; 8; 9 c VT320 International DECTID * reference: http://vt100.net/docs/vt100-ug/chapter3.html * "DA - Device Attributes" Response to the request described above * (VT100 to host) is generated by the VT100 as a DA control sequence * with the numeric parameters as follows: Option Present Sequence Sent * No options ESC [?1;0c Processor option (STP) ESC [?1;1c Advanced * video option (AVO) ESC [?1;2c AVO and STP ESC [?1;3c Graphics option * (GPO) ESC [?1;4c GPO and STP ESC [?1;5c GPO and AVO ESC [?1;6c GPO, * STP and AVO ESC [?1;7c * * * AVO = (132 character AND 24 lines - we can support that even if we * have to fake it. So - except for "ansi" emulation, our reply always * be include 1;2c */ if (daReply == null) { // @todo when we introduce line-drawing support, we must also // include: // String subcode = "61"; // default: vt100 String code = "1;2c"; // default: AVO if (terminalID.equalsIgnoreCase("vt320")) { subcode = "63;"; } else if (terminalID.equalsIgnoreCase("vt220") || terminalID.equalsIgnoreCase("linux")) { subcode = "62;"; } else if (terminalID.equalsIgnoreCase("ansi")) { code = "1;0c"; subcode = ""; } daReply = ((char) ESC) + "[?" + subcode + code; } write(daReply, false); flush(); } protected void setTermState(int term_state) { this.term_state = term_state; } /* hard reset the terminal */ public void resetTerminal() { gx[0] = 'B'; gx[1] = '0'; gx[2] = 'B'; gx[3] = 'B'; gl = 0; // default GL to G0 gr = 1; // default GR to G1 // reset tabs resetTabs(); } private int height, width; /* rows and columns */ private boolean altPrefixesMeta; private boolean remoteMarginSet; /** * Put a character on the screen with normal font and outline. The character * previously on that position will be overwritten. * * @param c * x-coordinate (column) * @param l * y-coordinate (line) * @param ch * the character to show on the screen * @see #insertChar * @see #deleteChar */ public void putChar(int c, int l, char ch) { putChar(c, l, ch, NORMAL); } /** * Put a character on the screen with specific font and outline. The * character previously on that position will be overwritten. * * @param c * x-coordinate (column) * @param l * y-coordinate (line) * @param ch * the character to show on the screen * @param attributes * the character attributes * @see #BOLD * @see #UNDERLINE * @see #INVERT * @see #NORMAL * @see #insertChar * @see #deleteChar */ public void putChar(int c, int l, char ch, long attributes) { c = checkBounds(c, 0, width - 1); l = checkBounds(l, 0, height - 1); terminalData[screenBase + l][c] = (attributes << 32) | ch; setLineDirty(screenBase + l); } /** * Get the character at the specified position. * * @param c * x-coordinate (column) * @param l * y-coordinate viewport/window (line) * @see #putChar */ public char getChar(int c, int l) { c = checkBounds(c, 0, width - 1); l = checkBounds(l, 0, height - 1); return (char) (terminalData[screenBase + l][c]); } /** * Get the attributes for the specified position. * * @param c * x-coordinate (column) * @param l * y-coordinate of viewport/window (line) * @return character attributes for specified location * @see #putChar */ public int getAttributes(int c, int l) { c = checkBounds(c, 0, width - 1); l = checkBounds(l, 0, height - 1); return (int) ((terminalData[screenBase + l][c] >> 32)); } /** * Insert a character at a specific position on the screen. All character * right to from this position will be moved one to the right. * * @param c * x-coordinate (column) * @param l * y-coordinate viewport/window (line) * @param ch * the character to insert * @param attributes * the character attributes * @see #BOLD * @see #UNDERLINE * @see #INVERT * @see #NORMAL * @see #putChar * @see #deleteChar */ private void insertChar(int c, int l, char ch, int attributes) { c = checkBounds(c, 0, width - 1); l = checkBounds(l, 0, height - 1); System.arraycopy(terminalData[screenBase + l], c, terminalData[screenBase + l], c + 1, width - c - 1); putChar(c, l, ch, attributes); } /** * Delete a character at a given position on the screen. All characters * right to the position will be moved one to the left. * * @param c * x-coordinate (column) * @param l * y-coordinate (line) * @see #putChar * @see #insertChar */ private void deleteChar(int c, int l) { // Logger.debug("emu.deleteChar: " + l); c = checkBounds(c, 0, width - 1); l = checkBounds(l, 0, height - 1); if (c < width - 1) { System.arraycopy(terminalData[screenBase + l], c + 1, terminalData[screenBase + l], c, width - c - 1); } putChar(width - 1, l, (char) 0); } /** * Put a String at a specific position giving all characters the same * attributes. Any characters previously on that position will be * overwritten. * * @param c * x-coordinate (column) * @param l * y-coordinate (line) * @param s * the string to be shown on the screen * @param attributes * character attributes * @see #BOLD * @see #UNDERLINE * @see #INVERT * @see #NORMAL * @see #putChar * @see #insertLine * @see #deleteLine */ // private void putString(int c, int l, String s, int attributes) { // for (int i = 0; i < s.length() && c + i < width; i++) { // putChar(c + i, l, s.charAt(i), attributes); // } // } /** * Insert a blank line at a specific position. Scroll text according to the * argument. * * @param l * the y-coordinate to insert the line * @param scrollDown * scroll down * @see #deleteLine * @see #SCROLL_UP * @see #SCROLL_DOWN */ private void insertLine(int l, boolean scrollDown) { insertLine(l, 1, scrollDown); } /** * Insert blank lines at a specific position. The current line and all * previous lines are scrolled one line up. The top line is lost. * * @param line * the y-coordinate to insert the line * @param n * number of lines to be inserted * @param scrollDown * scroll down * @see #deleteLine * @see #SCROLL_UP * @see #SCROLL_DOWN */ private void insertLine(int line, int n, boolean scrollDown) { // Logger.debug("emu.insertLine: " + line + " / " + n + " / " + // scrollDown); line = checkBounds(line, 0, height - 1); long cbuf[][] = null; int offset = 0; int oldBase = screenBase; // We do not scroll below bottom margin (below the scrolling region). if (line > bottomMargin) { return; } int top = (line < topMargin ? 0 : (line > bottomMargin ? (bottomMargin + 1 < height ? bottomMargin + 1 : height - 1) : topMargin)); int bottom = (line > bottomMargin ? height - 1 : (line < topMargin ? (topMargin > 0 ? topMargin - 1 : 0) : bottomMargin)); // System.out.println("l is " + l + ", top is " + top + ", bottom is " + // bottom + // "bottomargin is " + bottomMargin + ", topMargin is " + topMargin); if (scrollDown) { if (n > (bottom - top)) { n = (bottom - top); } cbuf = new long[bottom - line - (n - 1)][width]; // WTF - why are we reallocating for a simple insert? System.arraycopy(terminalData, oldBase + line, cbuf, 0, bottom - line - (n - 1)); System.arraycopy(cbuf, 0, terminalData, oldBase + line + n, bottom - line - (n - 1)); cbuf = terminalData; } else { try { if (n > (bottom - top) + 1) { n = (bottom - top) + 1; } if (bufSize < maxBufSize) { // So what are we doing here... // if the buffer will exceed teh allowed size after the // insertion, // we need to push the topmost content of the buffer out // from // underneath. if (bufSize + n > maxBufSize) { offset = n - (maxBufSize - bufSize); bufSize = maxBufSize; screenBase = maxBufSize - height - 1; windowBase = screenBase; } else { screenBase += n; windowBase += n; bufSize += n; } // @todo - always allocate max buffsize // this way we can shift without a re-alloc/copy every time. // ? use a linked list for easy insertion and deletion of // lines? // the only drawback is that we often start at a non-zero // position - can maintain this with a topRow reference. cbuf = new long[bufSize][width]; } else { offset = n; cbuf = terminalData; } // copy anything from the top of the buffer (+offset) to the new // top up to the screenBase. if (oldBase > 0) { System.arraycopy(terminalData, offset, cbuf, 0, oldBase - offset); } // copy anything from the top of the screen (screenBase) up to // the // topMargin to the new screen if (top > 0) { System.arraycopy(terminalData, oldBase, cbuf, screenBase, top); } // copy anything from the topMargin up to the amount of lines // inserted // to the gap left over between scrollback buffer and screenBase if (oldBase > 0) { System.arraycopy(terminalData, oldBase + top, cbuf, oldBase - offset, n); } for (int i = 0; i < line - top - (n - 1); i++) { cbuf[screenBase + top + i] = terminalData[oldBase + top + n + i]; } // // copy the all lines next to the inserted to the new buffer if (line < height - 1) { System.arraycopy(terminalData, oldBase + line + 1, cbuf, screenBase + line + 1, (height - 1) - line); } } catch (ArrayIndexOutOfBoundsException e) { Logger.error(e + " : " + e.getMessage()); // (comment from Meissner, apparently) // this should not happen anymore, but I will leave the code // here in case something happens anyway. That code above is // so complex I always have a hard time understanding what // I did, even though there are comments } } // this is a little helper to mark the scrolling // @todo - how many times do we reallocate this w/in the one routine?! for (int i = 0; i < n; i++) { int add = (scrollDown ? i : -i); cbuf[(screenBase + line) + add] = new long[width]; } terminalData = cbuf; if (scrollDown) { setLineDirty(line, bottom - line + 1); } else { setLineDirty(top, line - top + 1); } } /** * Delete a line at a specific position. Subsequent lines will be scrolled * up to fill the space and a blank line is inserted at the end of the * screen. * * @param l * the y-coordinate to insert the line * @see #deleteLine */ private void deleteLine(int l) { // Logger.debug("emu.deleteLine: " + l); l = checkBounds(l, 0, height - 1); int bottom = (l > bottomMargin ? height - 1 : (l < topMargin ? topMargin : bottomMargin + 1)); System.arraycopy(terminalData, screenBase + l + 1, terminalData, screenBase + l, bottom - l - 1); terminalData[screenBase + bottom - 1] = new long[width]; setLineDirty(l, bottom - l); } /** * Clear the specified attribute for the given line and column. Line and * column are assumed to be valid * * @param line * a valid line at buffer (not viewport) level. * @param col * a valid column. * @param attribute * attribute to clear. */ private void clearAttribute(int line, int col, int attribute) { terminalData[line][col] &= (((~attribute) << 32) | 0xFFFFFFFF); } /** * Clear the specified attribute for the given line and column. Line and * column are assumed to be valid * * @param line * a valid line at the buffer (not viewport) level. * @param col * a valid column. * @param attribute * attribute to set. * @see #BOLD * @see #INVERT * @see #UNDERLINE */ private void setAttribute(int line, int col, int attribute) { terminalData[line][col] |= (attribute << 32); } /* * private void setXAttr(int line, int col, int attribute) { * terminalData[line][col] |= attribute; } * * private void clearXAttr(int line, int col, int attribute) { * terminalData[line][col] &= ~attribute; } */ /** * Toggle the state of the specifeid attribute at the specified position. * This requires valid inputs, as it performs no validation itself. * * @param line * @param col * @param attribute */ private void toggleAttribute(int line, int col, int attribute) { if ((terminalData[screenBase + line][col] & (attribute << 32)) > 0) { clearAttribute(line, col, attribute); } else { setAttribute(line, col, attribute); } } /** * Sets the provided attribute across multiple lines, in a wrapping fashion. * That is, every character between startline,startcol and endline,endcol * will be marked as selected/deselected. Lines that fall in between * startline and endline will be selected /deselected in full. * * @param startLine * line to begin toggle, WRT the viewport. * @param startCol * starting column of region, within startLine * @param endLine * last line to toggle, WRT the viewport. * @param endCol * ending column to toggle, within endLine * @param attribute * the attribute to reverse * @see #setAttribute * @see #clearAttribute * @author mparadise */ public void toggleAttributeStateForRange(int startLine, int startCol, int endLine, int endCol, int attribute) { // @todo for performance, might a vector be better for lines only? // This would prevent a lot of array copies when we scroll/move startLine = checkBounds(startLine, 0, height - 1); endLine = checkBounds(endLine, 0, height - 1); startCol = checkBounds(startCol, 0, width - 1); endCol = checkBounds(endCol, 0, width - 1); int lineStep = endLine < startLine ? -1 : 1; int colStep = endCol < startCol ? -1 : 1; // Sanity check - don't allow invalid selection range. if (startLine == endLine) { int line = screenBase + startLine; for (int col = startCol; col < endCol; col += colStep) { toggleAttribute(line, col, attribute); } } else { int stopLine = endLine + screenBase; for (int line = startLine + screenBase; line < stopLine; line += lineStep) { int stopCol; int beginCol; if (line == startLine) { stopCol = width; beginCol = startCol; } else if (line == stopLine) { stopCol = endCol; beginCol = 0; } else { stopCol = width; beginCol = 0; } for (int col = beginCol; col < stopCol; col += colStep) { toggleAttribute(line, col, attribute); } } } setLineDirty(startLine, endLine - startLine); } /** * Returns a StringBuffer containing characters that have the specified * attribute(s). * * @param attribute * attribute(s) to check for. * @param addNewlines * if true, a newline will be appended to the return string every * time a character included is on a different line than the last * character included. * @return a string containing all characters with the specified attribute. * @author mparadise */ public synchronized String getCharactersWithAttributes(long attribute, boolean addNewlines) { // Over-allocate string buffer to contain the matching chars region, // with a newline separating, on the theory that it's better to // allocate too much than not enough (and have to do multiple // array copies internally to the stringbuffer. // Note that we're copying the visiible vertical range only. StringBuffer output = new StringBuffer((width + 1) * height); attribute = attribute << 32; int last = -1; int max = Math.min(windowBase + height, bufSize); for (int y = screenBase; y < max; y++) { for (int x = 0; x < width; x++) { if ((terminalData[y][x] & attribute) > 0) { // Append newline for each gap between lines // that have matching attributes. if (addNewlines) { if (last == -1) { last = y; } else if (last != y) { output.append("\n"); last = y; } } } output.append((char) (terminalData[y][x])); } } return output.toString(); } /** * Delete a rectangular portion of the screen. * * @param c * x-coordinate (column) * @param l * y-coordinate (row) * @param w * with of the area in characters * @param h * height of the area in characters * @param curAttr * attribute to fill * @see #deleteChar * @see #deleteLine */ private void deleteArea(int c, int l, int w, int h, long curAttr) { // Logger.debug("emu.deleteArea: " + c + " " + l + " " + w + " " + h); c = checkBounds(c, 0, width - 1); l = checkBounds(l, 0, height - 1); long attr = curAttr << 32; long cbuf[] = new long[w]; for (int i = 0; i < w; i++) { cbuf[i] = attr; } for (int i = 0; i < h && l + i < height; i++) { System.arraycopy(cbuf, 0, terminalData[screenBase + l + i], c, w); } setLineDirty(l, h); } /** * Sets whether the cursor is visible or not. * * @param doshow */ private void showCursor(boolean doshow) { // Logger.debug("emu.showCursor: " + doshow); if (doshow != showcursor) { setLineDirty(cursorY, 1); } showcursor = doshow; } /** * Puts the cursor at the specified position , assuming the values will be * interpreted as relative to the current viewport. * * @param c * column * @param l * line */ private void setCursorPosition(int c, int l) { if (l != cursorY || c != cursorX) setLineDirty(cursorY, 1); cursorX = checkBounds(c, 0, width - 1); cursorY = checkBounds(l, 0, height - 1); setLineDirty(cursorY, 1); } /** * @return total columns */ public int getWidth() { return this.width; } /** * Set the top scroll margin for the screen. If the current bottom margin is * smaller it will become the top margin and the line will become the bottom * margin. * * @param l * line that is the margin */ public void setTopMargin(int l) { // Logger.debug("emu.setTopMargin: " + l); if (l > bottomMargin) { topMargin = bottomMargin; bottomMargin = l; } else { topMargin = l; } if (topMargin < 0) { topMargin = 0; } if (bottomMargin > height - 1) { bottomMargin = height - 1; } } /** * Get the top scroll margin. */ public int getTopMargin() { return topMargin; } /** * Set the bottom scroll margin for the screen. If the current top margin is * bigger it will become the bottom margin and the line will become the top * margin. * * @param l * line that is the margin */ public void setBottomMargin(int l, boolean remote) { // Logger.debug("emu.setBottomMargin: " + l); if (l < topMargin) { bottomMargin = topMargin; topMargin = l; } else { bottomMargin = l; } if (topMargin < 0) { topMargin = 0; } if (bottomMargin > height - 1) { bottomMargin = height - 1; } remoteMarginSet = remoteMarginSet || remote; } /** * Get the bottom scroll margin. */ private int getBottomMargin() { return bottomMargin; } public void setScrollbackBufferSize(int scrollbackSize) { if (scrollbackSize < 1) return; setBufferSize(scrollbackSize + height); } /** * @param amount * new size of the buffer */ public void setBufferSize(int amount) { Logger.debug("emu.setBufferSize: " + amount); if (amount < height) { amount = height; } if (amount < maxBufSize) { long cbuf[][] = new long[amount][width]; int copyStart = bufSize - amount < 0 ? 0 : bufSize - amount; int copyCount = bufSize - amount < 0 ? bufSize : amount; if (terminalData != null) { System.arraycopy(terminalData, copyStart, cbuf, 0, copyCount); } terminalData = cbuf; bufSize = copyCount; screenBase = bufSize - height; windowBase = screenBase; } maxBufSize = amount; numScrollbackLines = maxBufSize - height; } /** * Retrieve current scrollback buffer size. * * @see #setBufferSize */ public int getBufferSize() { return bufSize; } /** * Retrieve maximum buffer Size. * * @see #getBufferSize */ public int getMaxBufferSize() { return maxBufSize; } /* * private void dumpKeyData(String message) { System.out.println(message); * System.out.println(" bufSize: " + bufSize); * System.out.println(" maxBufSize: " + maxBufSize); * System.out.println(" screenBase: " + screenBase); * System.out.println(" windowBase: " + windowBase); * System.out.println(" width: " + width); * System.out.println(" height: " + height); * System.out.println(" topMargin: " + topMargin); * System.out.println(" bottomMargin: " + bottomMargin); * System.out.println(" ROW: " + ROW); * System.out.println(" COL: " + COLUMN); * * } */ /** * Change the size of the screen. This will include adjustment of the * scrollback buffer. * * @param w * of the screen * @param h * of the screen * @param updateRemote * if true indicate that we should request a remote resizing. */ public void setScreenSize(int w, int h, boolean updateRemote) { Logger.debug("emu.setScreenSize: " + w + " / " + h); // dumpKeyData("setScreenSize: PRE RESIZE"); long cbuf[][]; int bsize = bufSize; if (w < 1 || h < 1) { return; } if (w == width && h == height) { Logger.debug("emu.setScreenSize: no change, not resizing"); return; } if (h > maxBufSize) { maxBufSize = h; } if (h > bufSize) { bufSize = h; screenBase = 0; windowBase = 0; } // int diff = screenBase - windowBase; if (windowBase + h >= bufSize) windowBase = bufSize - h; if (screenBase + h >= bufSize) screenBase = bufSize - h; // @todo - this is my attempt at cleaning up artifacts when we have a // smaller // screen ... of coruse this won't work, the artifacts are // Also, what happens when our screen gets resized to below our margins // - margins must move up. // but we have to look at the response to a resize - do we handle that, // or doe sthe remote host tell us our new // margins? // if (screenBase + h >= bufSize) { // screenBase = bufSize - h; // } else { // // Put the screen base in the same position relative to the bottom // // fo the buffer that it used to be. // screenBase = bufSize - (bsize - screenBase); // } // // if (windowBase + h >= bufSize) { // windowBase = bufSize - h; // } else { // // Put the window base in the same relative position to the // // screenbase that it was. // windowBase = screenBase - diff; // } cbuf = new long[bufSize][w]; int len = w < width ? w : width; if (terminalData != null) { for (int i = 0; i < bsize && i < bufSize; i++) { System.arraycopy(terminalData[i], 0, cbuf[i], 0, len); terminalData[i][0] |= XATTR_DIRTY; } long[] empty = new long[w]; // Mark the remaining un-used lines dirty as well - and clear them // out sot aht they don't remain on screen. for (int i = bsize; i < bufSize; i++) { System.arraycopy(empty, 0, cbuf[i], 0, w); cbuf[i][0] |= XATTR_DIRTY; } } terminalData = cbuf; // Don't send an update unless we actually change dimensions. updateRemote = updateRemote && (width != w || height != h); // Okay - if height changes, we need to modify our bottom margin // so that it keeps a relative position that's the same width = w; height = h; topMargin = 0; // First reset our top margin. The new screen dimensions could run afoul // of other limts - this makes sure those boundaries get applied setTopMargin(topMargin); // Similarly - if we have not already set up the bottom margin, we'll do // that now; but // if we hae alrady set it we can't adjust it -- because the remote // client wouldn't know // about the adjustment., Instead, we'll re-set it to its current value // -- this will // ensure that we check to make sure it's within boundaries. (Such as // when the terminal size is smaller // than it was.) if (remoteMarginSet) { setBottomMargin(bottomMargin, false); } else { // if we neever set margin remotely, we're maintaining it manualy // until then,. bottomMargin = height - 1; } // Force a refresh if (updateRemote) { resize(); } // dumpKeyData("setScreenSize: POST RESIZE"); } /** * Mark lines to be updated by the renderer * * @param l * starting line * @param n * amount of lines to be updated */ public void setLineDirty(int l, int n) { // @todo by expanding attributes to long, we can just use // a attribute here and save the extra array. // Or by using a Character class... either way will give us // character-level dirty controls. l = checkBounds(l, 0, height - 1); int max = Math.min(l + n, height); for (int line = l; line < max; line++) { terminalData[line][0] |= XATTR_DIRTY; } } public boolean isLineDirty(int line) { line = checkBounds(line, 0, height - 1); return (terminalData[line][0] & XATTR_DIRTY) > 0; } public void setLineDirty(int line) { line = checkBounds(line, 0, height - 1); terminalData[line][0] |= XATTR_DIRTY; } public void setLineClean(int line) { int newline = line = checkBounds(line, 0, height - 1); terminalData[newline][0] &= (~XATTR_DIRTY); } private int checkBounds(int value, int lower, int upper) { if (value < lower) { return lower; } if (value > upper) { return upper; } return value; } public int getTerminalWidth() { return width; } public int getTerminalHeight() { return height; } /** * By default we use VT100 settings for function keys - which use a * different code for F1-F4. However, many other emulations use a different * set that's more consistent with F5-F12. This will enable that * alternative. */ public void setFunctionKeyMode(int mode) { switch (mode) { case FK_VT100: FunctionKey[1] = "\u001bOP"; FunctionKey[2] = "\u001bOQ"; FunctionKey[3] = "\u001bOR"; FunctionKey[4] = "\u001bOS"; break; case FK_LINUX_APP_KEYPAD: FunctionKey[1] = "\u001b[11~"; FunctionKey[2] = "\u001b[12~"; FunctionKey[3] = "\u001b[13~"; FunctionKey[4] = "\u001b[14~"; break; case FK_LINUX: FunctionKey[1] = "\u001b[[A"; FunctionKey[2] = "\u001b[[B"; FunctionKey[3] = "\u001b[[C"; FunctionKey[4] = "\u001b[[D"; break; } } public void enableAltSendsMeta() { altPrefixesMeta = true; } public String getBufferString() { StringBuffer output = new StringBuffer((width + 1) * (height + 1)); char c; int max = Math.min(windowBase + height, bufSize); for (int y = windowBase; y < max; y++) { for (int x = 0; x < width; x++) { c = (char) terminalData[y][x]; if (c < ' ') continue; output.append(c); } // As long as this isn't a soft-wrapped line, insert a newline if ((terminalData[y][width - 1] & XATTR_WRAP) == 0) output.append("\n"); } return output.toString(); } /** * Invoked when dimensions have changed Temporarily kludge perhaps... */ public abstract void resize(); /** * Synchronize on this mutex when performing read operations out of the * terminal backing store. * * @return mutex object */ public Object getTermBufferMutex() { return this.termBufferMutex; } public void refreshCursorPosition() { setCursorPosition(COLUMN, ROW); } public void terminate() { // Perform any cleanup. } }