/////////////////////////////////////////////////////////////////////////////// // VT100.js -- JavaScript based terminal emulator // Copyright (C) 2008-2009 Markus Gutschke // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // 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., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // // In addition to these license terms, the author grants the following // additional rights: // // If you modify this program, or any covered work, by linking or // combining it with the OpenSSL project's OpenSSL library (or a // modified version of that library), containing parts covered by the // terms of the OpenSSL or SSLeay licenses, the author // grants you additional permission to convey the resulting work. // Corresponding Source for a non-source form of such a combination // shall include the source code for the parts of OpenSSL used as well // as that of the covered work. // // You may at your option choose to remove this additional permission from // the work, or from any part of it. // // It is possible to build this program in a way that it loads OpenSSL // libraries at run-time. If doing so, the following notices are required // by the OpenSSL and SSLeay licenses: // // This product includes software developed by the OpenSSL Project // for use in the OpenSSL Toolkit. (http://www.openssl.org/) // // This product includes cryptographic software written by Eric Young // (eay@cryptsoft.com) // // // The most up-to-date version of this program is always available from // http://shellinabox.com // // // Notes: // // The author believes that for the purposes of this license, you meet the // requirements for publishing the source code, if your web server publishes // the source in unmodified form (i.e. with licensing information, comments, // formatting, and identifier names intact). If there are technical reasons // that require you to make changes to the source code when serving the // JavaScript (e.g to remove pre-processor directives from the source), these // changes should be done in a reversible fashion. // // The author does not consider websites that reference this script in // unmodified form, and web servers that serve this script in unmodified form // to be derived works. As such, they are believed to be outside of the // scope of this license and not subject to the rights or restrictions of the // GNU General Public License. // // If in doubt, consult a legal professional familiar with the laws that // apply in your country. /////////////////////////////////////////////////////////////////////////////// // // As modified by Mark G.Daniel under the same license conditions. // // 31-MAY-2021 MGD cookie deprecated without 'SameSite=strict;secure;' // bugfix; VT100.prototype.styleSpan() always "= style" // 12-SEP-2020 MGD VT220-style function keypad (F1..F12,F16..F19) // 19-NOV-2016 MGD VT220-style editing and numeric keypad mapping // 18-JUL-2016 MGD reformatting and significant enhancements // 04-SEP-2014 MGD calculate character width more precisely // 14-JUL-2014 MGD audible bell // 20-DEC-2013 MGD fix for Firefox [-_] keycode // 14-JUL-2013 MGD a few bugfixes // 17-DEC-2011 MGD adaptation for DCLinabox // /////////////////////////////////////////////////////////////////////////////// // #define ESnormal 0 // #define ESesc 1 // #define ESsquare 2 // #define ESgetpars 3 // #define ESgotpars 4 // #define ESdeviceattr 5 // #define ESfunckey 6 // #define EShash 7 // #define ESsetG0 8 // #define ESsetG1 9 // #define ESsetG2 10 // #define ESsetG3 11 // #define ESbang 12 // #define ESpercent 13 // #define ESignore 14 // #define ESnonstd 15 // #define ESpalette 16 // #define ESstatus 17 // #define ESss2 18 // #define ESss3 19 // #define ESsquote 20 // #define ATTR_DEFAULT 0x00F0 // #define ATTR_REVERSE 0x0100 // #define ATTR_UNDERLINE 0x0200 // #define ATTR_DIM 0x0400 // #define ATTR_BRIGHT 0x0800 // #define ATTR_BLINK 0x1000 // #define MOUSE_DOWN 0 // #define MOUSE_UP 1 // #define MOUSE_CLICK 2 /////////////////////////////////////////////////////////////////////////////// function VT100(container) { this.themeLight = true; this.themeDark = this.themeGreen = this.themeLocal = false; this.otherOptions = false; this.logStream = false; this.writeDelay = 0; if (typeof DCLinaboxTheme != 'undefined') { this.themeLight = this.themeDark = this.themeGreen = this.themeLocal = false; if (DCLinaboxTheme == 'light') this.themeLight = true; if (DCLinaboxTheme == 'dark') this.themeDark = true; if (DCLinaboxTheme == 'green') this.themeGreen = true; if (DCLinaboxTheme == 'local') this.themeLocal = true; if (!(this.themeLight || this.themeDark || this.themeGreen || this.themeLocal)) this.themeLight = true; } this.getUserSettings(); this.initializeElements(container); this.maxScrollbackLines = 1000; this.npar = 0; this.par = [ ]; this.isQuestionMark = false; this.savedX = [ ]; this.savedY = [ ]; this.savedAttr = [ ]; this.savedUseGMap = 0; this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap, this.CodePage437Map, this.DirectToFontMap ]; this.savedValid = [ ]; this.respondString = ''; this.statusString = ''; this.internalClipboard = undefined; this.reset(true); } /////////////////////////////////////////////////////////////////////////////// VT100.prototype.reset = function(clearHistory) { this.isEsc = 0 /* ESnormal */; this.needWrap = false; this.autoWrapMode = true; this.dispCtrl = false; this.toggleMeta = false; this.insertMode = false; this.applKeyMode = false; this.cursorKeyMode = false; this.crLfMode = false; this.offsetMode = false; this.printing = false; this.mouseReporting = false; this.DECelr = 0; this.DECsle = 0; this.mutton = 0; if (typeof this.printWin != 'undefined' && this.printWin && !this.printWin.closed) { this.printWin.close(); } this.printWin = null; this.utfEnabled = this.utfPreferred; this.utfCount = 0; this.utfChar = 0; this.style = ''; this.attr = this.attrTheme = 0; this.useGMap = 0; this.GMap = [ this.Latin1Map, this.VT100GraphicsMap, this.CodePage437Map, this.DirectToFontMap ]; this.translate = this.GMap[this.useGMap]; this.top = 0; this.bottom = this.terminalHeight; this.lastCharacter = ' '; this.userTabStop = [ ]; /* in case both have been set to true - Mac prevails :-) */ if (this.vmsKeysMac) this.vmsKeysPC = false; if (this.themeDark) this.toggleDarkBackground(); else if (this.themeGreen) this.toggleGreenScreen(); else if (this.themeLocal) this.toggleThemeLocal(); else this.toggleLightBackground(); if (clearHistory) { for (var i = 0; i < 2; i++) { while (this.crt[i].firstChild) { this.crt[i].removeChild(this.crt[i].firstChild); } } } this.enableAlternateScreen(false); this.gotoXY(0, 0); this.showCursor(); this.isInverted = false; this.flashInverted = false; this.updateStyle(); this.refreshInvertedState(); this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.color, this.style); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.addListener = function(elem, event, listener) { if (elem.addEventListener) { elem.addEventListener(event, listener, false); } else { elem.attachEvent('on' + event, listener); } }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.getUserSettings = function() { // Compute hash signature to identify the entries in the userCSS menu. // If the menu is unchanged from last time, default values can be // looked up in a cookie associated with this page. this.signature = 1; this.loginPrompt = false; this.utfPreferred = false; this.audibleBell = false; this.autoprint = true; if (typeof DCLinaboxVisualBell != 'undefined') { if (DCLinaboxVisualBell == true) this.visualBell = true; if (DCLinaboxVisualBell == false) this.visualBell = false; } else this.visualBell = typeof suppressAllAudio != 'undefined' && suppressAllAudio; if (this.visualBell) { this.signature = Math.floor(16807*this.signature + 1) % ((1 << 31) - 1); } if (typeof userOptionList != 'undefined') { for (var i = 0; i < userOptionList.length; ++i) { var label = userOptionList[i][0]; for (var j = 0; j < label.length; ++j) { this.signature = Math.floor(16807*this.signature + label.charCodeAt(j)) % ((1 << 31) - 1); } if (userOptionList[i][1]) { this.signature = Math.floor(16807*this.signature + 1) % ((1 << 31) - 1); } } } var numberOfSettings = 11; var key = 'DCLinabox=' + this.signature + ':'; var settings = document.cookie.indexOf(key); if (settings >= 0) { settings = document.cookie.substr(settings + key.length). replace(/([0-1]*).*/, "$1"); if (settings.length == numberOfSettings + (typeof userOptionList == 'undefined' ? 0 : userOptionList.length)) { this.loginPrompt = settings.charAt(0) != '0'; this.utfPreferred = settings.charAt(1) != '0'; this.audibleBell = settings.charAt(2) != '0'; this.visualBell = settings.charAt(3) != '0'; this.autoprint = settings.charAt(4) != '0'; this.vmsKeysPC = settings.charAt(5) != '0'; this.vmsKeysMac = settings.charAt(6) != '0'; this.themeLight = settings.charAt(7) != '0'; this.themeDark = settings.charAt(8) != '0'; this.themeGreen = settings.charAt(9) != '0'; this.themeLocal = settings.charAt(10) != '0'; } } else if (this.DCLinaboxVMSkeysMac) this.vmsKeysMac = true; else if (this.DCLinaboxVMSkeysPC) this.vmsKeysPC = true; else if (navigator.platform.indexOf('Mac') >= 0) this.vmsKeysMac = true; else this.vmsKeysPC = true; this.utfEnabled = this.utfPreferred; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.storeUserSettings = function() { var settings = 'DCLinabox=' + this.signature + ':' + (this.loginPrompt ? '1' : '0') + (this.utfPreferred ? '1' : '0') + (this.audibleBell ? '1' : '0') + (this.visualBell ? '1' : '0') + (this.autoprint ? '1' : '0') + (this.vmsKeysPC ? '1' : '0') + (this.vmsKeysMac ? '1' : '0') + (this.themeLight ? '1' : '0') + (this.themeDark ? '1' : '0') + (this.themeGreen ? '1' : '0') + (this.themeLocal ? '1' : '0'); var d = new Date(); d.setDate(d.getDate() + 3653); document.cookie = settings + ' SameSite=Strict; Secure; expires=' + d.toGMTString(); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.initializeElements = function(container) { // If the necessary objects have not already been defined in the HTML // page, create them now. if (container) { this.container = container; } else if (!(this.container = document.getElementById('vt100'))) { this.container = document.createElement('div'); this.container.id = 'vt100'; document.body.appendChild(this.container); } if (!this.getChildById(this.container, 'menu') || !this.getChildById(this.container, 'scrollable') || // |crt| and |alt_crt| were |console| in original vt100.js !this.getChildById(this.container, 'crt') || !this.getChildById(this.container, 'alt_crt') || !this.getChildById(this.container, 'ieprobe') || !this.getChildById(this.container, 'padding') || !this.getChildById(this.container, 'cursor') || !this.getChildById(this.container, 'lineheight') || !this.getChildById(this.container, 'usercss') || !this.getChildById(this.container, 'space') || !this.getChildById(this.container, 'input') || !this.getChildById(this.container, 'cliphelper')) { // Only enable the "embed" object, if we have a suitable plugin. // Otherwise, we might get a pointless warning that a suitable // plugin is not yet installed. If in doubt, just stay silent. var embed = ''; try { if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name != 'undefined') { embed = (typeof suppressAllAudio != 'undefined' && suppressAllAudio) ? '' : ''; } } catch (e) { } this.container.innerHTML = '' + '' + '
' + '
 
' + '
' +
            '
' +
            '
 
' + '
' + '' + '
' + '
' + charsABC + '
' + '
' + ''; } // Find the object used for playing the "beep" sound, if any. if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) { this.beeper = undefined; } else { this.beeper = this.getChildById(this.container, 'beep_embed'); if (!this.beeper || !this.beeper.Play) { this.beeper = this.getChildById(this.container, 'beep_bgsound'); if (!this.beeper || typeof this.beeper.src == 'undefined') { this.beeper = undefined; } } } // Initialize the variables for finding the text crt and the cursor. this.curSizeBox = this.getChildById(this.container, 'cursize'); this.menu = this.getChildById(this.container, 'menu'); this.scrollable = this.getChildById(this.container, 'scrollable'); this.lineheight = this.getChildById(this.container, 'lineheight'); this.crt = [ this.getChildById(this.container, 'crt'), this.getChildById(this.container, 'alt_crt') ]; var ieProbe = this.getChildById(this.container, 'ieprobe'); this.padding = this.getChildById(this.container, 'padding'); this.cursor = this.getChildById(this.container, 'cursor'); this.usercss = this.getChildById(this.container, 'usercss'); this.space = this.getChildById(this.container, 'space'); this.input = this.getChildById(this.container, 'input'); this.cliphelper = this.getChildById(this.container, 'cliphelper'); // Remember the dimensions of a standard character glyph. We would // expect that we could just check cursor.clientWidth/Height at any time // but it turns out that browsers sometimes invalidate these values // (e.g. while displaying a print preview screen). this.cursorWidth = (this.cursor.clientWidth+1) / charsABC.length; this.cursorWidth10 = this.cursorWidth + (this.cursorWidth / 10); /* +10% */ this.cursorHeight = this.lineheight.clientHeight; // IE has a slightly different boxing model, that we need to compensate for this.isIE = ieProbe.offsetTop > 1; ieProbe = undefined; this.crt.innerHTML = ''; // Determine if the terminal window is positioned at the beginning of the // page, or if it is embedded somewhere else in the page. For full-screen // terminals, automatically resize whenever the browser window changes. var marginTop = parseInt(this.getCurrentComputedStyle( document.body, 'marginTop')); var marginLeft = parseInt(this.getCurrentComputedStyle( document.body, 'marginLeft')); var marginRight = parseInt(this.getCurrentComputedStyle( document.body, 'marginRight')); var x = this.container.offsetLeft; var y = this.container.offsetTop; for (var parent = this.container; parent = parent.offsetParent; ) { x += parent.offsetLeft; y += parent.offsetTop; } this.isEmbedded = marginTop != y || marginLeft != x || (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) - marginRight != x + this.container.offsetWidth; // always embedded for DCLinabox if (typeof DCLinaboxVersion != 'undefined') this.isEmbedded = true; if (!this.isEmbedded) { // Some browsers generate resize events when the terminal is first // shown. Disable showing the size indicator until a little bit after // the terminal has been rendered the first time. this.indicateSize = false; setTimeout(function(vt100) { return function() { vt100.indicateSize = true; }; }(this), 100); this.addListener(window, 'resize', function(vt100) { return function() { vt100.hideContextMenu(); vt100.resizer(); }; }(this)); // Hide extra scrollbars attached to window document.body.style.margin = '0px'; try { document.body.style.overflow ='hidden'; } catch (e) { } try { document.body.oncontextmenu = function() {return false;};} catch(e){} } // Hide context menu this.hideContextMenu(); // Add input listeners this.addListener(this.input, 'blur', function(vt100) { return function() { vt100.blurCursor(); } }(this)); this.addListener(this.input, 'focus', function(vt100) { return function() { vt100.focusCursor(); } }(this)); this.addListener(this.input, 'keydown', function(vt100) { return function(e) { if (!e) e = window.event; return vt100.keyDown(e); }; }(this)); this.addListener(this.input, 'keypress', function(vt100) { return function(e) { if (!e) e = window.event; return vt100.keyPressed(e); }; }(this)); this.addListener(this.input, 'keyup', function(vt100) { return function(e) { if (!e) e = window.event; return vt100.keyUp(e); }; }(this)); // Attach listeners that move the focus to the field. // This way we can make sure that we can receive keyboard input. var mouseEvent = function(vt100, type) { return function(e) { if (!e) e = window.event; return vt100.mouseEvent(e, type); }; }; this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */)); this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */)); this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */)); // Initialize the blank terminal window. this.spacesBuffer = ''; this.currentScreen = 0; this.cursorX = 0; this.cursorY = 0; this.numScrollbackLines = 0; this.top = 0; this.bottom = 0x7FFFFFFF; this.resizer(); this.focusCursor(); this.input.focus(); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.getChildById = function(parent, id) { var nodeList = parent.all || parent.getElementsByTagName('*'); if (typeof nodeList.namedItem == 'undefined') { for (var i = 0; i < nodeList.length; i++) { if (nodeList[i].id == id) { return nodeList[i]; } } return null; } else { var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id); return elem ? elem[0] || elem : null; } }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.getCurrentComputedStyle = function(elem, style) { if (typeof elem.currentStyle != 'undefined') { return elem.currentStyle[style]; } else { return document.defaultView.getComputedStyle(elem, null)[style]; } }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.repairElements = function(crt) { for (var line = crt.firstChild; line; line = line.nextSibling) { if (!line.clientHeight) { var newLine = document.createElement(line.tagName); newLine.style.cssText = line.style.cssText; newLine.className = line.className; newLine.DECstyle = line.DECstyle; if (line.tagName == 'DIV') { for (var span = line.firstChild; span; span = span.nextSibling) { var newSpan = document.createElement(span.tagName); newSpan.style.cssText = span.style.cssText; newSpan.className = span.className; this.setTextContent(newSpan, this.getTextContent(span)); newLine.appendChild(newSpan); } } else { this.setTextContent(newLine, this.getTextContent(line)); } line.parentNode.replaceChild(newLine, line); line = newLine; } } }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.resized = function(w, h) { }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.resizer = function() { // Cursor can get corrupted if the print-preview is displayed in Firefox. // Recreating it, will repair it. var newCursor = document.createElement('pre'); this.setTextContent(newCursor, ' '); newCursor.id = 'cursor'; newCursor.style.cssText = this.cursor.style.cssText; this.cursor.parentNode.insertBefore(newCursor, this.cursor); if (!newCursor.clientHeight) { // Things are broken right now. This is probably because we are // displaying the print-preview. Just don't change any of our settings // until the print dialog is closed again. newCursor.parentNode.removeChild(newCursor); return; } else { // Swap the old broken cursor for the newly created one. this.cursor.parentNode.removeChild(this.cursor); this.cursor = newCursor; } // Really horrible things happen if the contents of the terminal changes // while the print-preview is showing. We get HTML elements that show up // in the DOM, but that do not take up any space. Find these elements and // try to fix them. this.repairElements(this.crt[0]); this.repairElements(this.crt[1]); // Lock the cursor size to the size of a normal character. This helps with // characters that are taller/shorter than normal. Unfortunately, we will // still get confused if somebody enters a character that is wider/narrower // than normal. This can happen if the browser tries to substitute a // characters from a different font. this.cursor.style.width = this.cursorWidth + 'px'; this.cursor.style.height = this.cursorHeight + 'px'; // Adjust height for one pixel padding of the #vt100 element. // The latter is necessary to properly display the inactive cursor. var crt = this.crt[this.currentScreen]; var height = (this.isEmbedded ? this.container.clientHeight : (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight))-1; var partial = height % this.cursorHeight; this.scrollable.style.height = (height > 0 ? height : 0) + 'px'; this.padding.style.height = (partial > 0 ? partial : 0) + 'px'; var oldTerminalHeight = this.terminalHeight; this.updateWidth(); this.updateHeight(); // Clip the cursor to the visible screen. var cx = this.cursorX; var cy = this.cursorY + this.numScrollbackLines; // The alternate screen never keeps a scroll back buffer. this.updateNumScrollbackLines(); while (this.currentScreen && this.numScrollbackLines > 0) { crt.removeChild(crt.firstChild); this.numScrollbackLines--; } cy -= this.numScrollbackLines; if (cx < 0) { cx = 0; } else if (cx > this.terminalWidth) { cx = this.terminalWidth - 1; if (cx < 0) { cx = 0; } } if (cy < 0) { cy = 0; } else if (cy > this.terminalHeight) { cy = this.terminalHeight - 1; if (cy < 0) { cy = 0; } } // Clip the scroll region to the visible screen. if (this.bottom > this.terminalHeight || this.bottom == oldTerminalHeight) { this.bottom = this.terminalHeight; } if (this.top >= this.bottom) { this.top = this.bottom-1; if (this.top < 0) { this.top = 0; } } // Truncate lines, if necessary. Explicitly reposition cursor (this is // particularly important after changing the screen number), and reset // the scroll region to the default. this.truncateLines(this.terminalWidth); this.putString(cx, cy, '', undefined, undefined); this.scrollable.scrollTop = this.numScrollbackLines * this.cursorHeight + 1; // Update classNames for lines in the scrollback buffer var line = crt.firstChild; for (var i = 0; i < this.numScrollbackLines; i++) { line.className = 'scrollback'; line = line.nextSibling; } while (line) { line.className = ''; line = line.nextSibling; } // Send notification that the window size has been changed this.resized(this.terminalWidth, this.terminalHeight); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.selection = function() { try { return '' + (window.getSelection && window.getSelection() || document.selection && document.selection.type == 'Text' && document.selection.createRange().text || ''); } catch (e) { } return ''; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.cancelEvent = function(event) { if (this.isEmbedded) { if (this.embeddedOnContextMenu == null) this.embeddedOnContextMenu = document.body.oncontextmenu; try { document.body.oncontextmenu = function() { return false; }; } catch(e) { } setTimeout("document.body.oncontextmenu = \ this.embeddedOnContextMenu;",500); } try { // For non-IE browsers event.stopPropagation(); event.preventDefault(); } catch (e) { } try { // For IE event.cancelBubble = true; event.returnValue = false; // event.button = 0; } catch (e) { } return false; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.mouseEvent = function(event, type) { // If any text is currently selected, do not move the focus as that would // invalidate the selection. var selection = this.selection(); if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) { this.input.focus(); } // Compute mouse position in characters. var offsetX = this.container.offsetLeft; var offsetY = this.container.offsetTop; for (var e = this.container; e = e.offsetParent; ) { offsetX += e.offsetLeft; offsetY += e.offsetTop; } var x = (event.clientX - offsetX) / this.cursorWidth; var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) / this.cursorHeight - this.numScrollbackLines; var inside = true; if (x >= this.terminalWidth) { x = this.terminalWidth - 1; inside = false; } if (x < 0) { x = 0; inside = false; } if (y >= this.terminalHeight) { y = this.terminalHeight - 1; inside = false; } if (y < 0) { y = 0; inside = false; } // Compute button number and modifier keys. var button = type != 0 /* MOUSE_DOWN */ ? 3 : typeof event.pageX != 'undefined' ? event.button : [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button]; if (button != undefined) { if (event.shiftKey) { button |= 0x04; } if (event.altKey || event.metaKey) { button |= 0x08; } if (event.ctrlKey) { button |= 0x10; } } // left mouse button down usually introduces text selection (for copy) if (button == 0 && !event.shiftKey && !event.metaKey && !event.ctrlKey) this.pruneAllWhiteSpace(); /* DECRQLP button event and bitmask (see .csiDECRQLP below) */ var bevent = 0; if (type == 0) { switch (event.button) { case 0 : this.mutton |= 0x04; bevent = 2; break; case 1 : this.mutton |= 0x02; bevent = 4; break; case 2 : this.mutton |= 0x01; bevent = 6; break; } } else if (type == 1) { switch (event.button) { case 0 : this.mutton &= 0x03; bevent = 3; break; case 1 : this.mutton &= 0x05; bevent = 5; break; case 2 : this.mutton &= 0x06; bevent = 7; break; } } // Report mouse events if they happen inside of the current screen and // with the SHIFT key unpressed. Both of these restrictions do not apply // for button releases, as we always want to report those. if (this.mouseReporting && !selection.length && (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) { if (inside || type != 0 /* MOUSE_DOWN */) { if (button != undefined) { if (this.DECelr == 2) { /* a one-shot report */ this.mouseReporting = false; this.DECelr = 0; } var report = '\u001B['; report += bevent.toString(); report += ';'; report += this.mutton.toString(); report += ';'; report += Math.floor(y+1).toString(); report += ';'; report += Math.floor(x+1).toString(); report += ';'; report += 0; report += '&w'; if (type != 2 /* MOUSE_CLICK */) { this.keysPressed(report); } // If we reported the event, stop propagating it (not sure, // if this actually works on most browsers; blocking the global // "oncontextmenu" even is still necessary). return this.cancelEvent(event); } } } // bring up context menu if (button == 2 && !event.shiftKey) { /* unless DECELR (event locator reporting) enabled */ if (!this.DECelr) { if (type == 0 /* MOUSE_DOWN */) { this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY); } } return this.cancelEvent(event); } if (this.mouseReporting) { try { event.shiftKey = false; } catch (e) { } } return true; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.replaceChar = function(s, ch, repl) { for (var i = -1;;) { i = s.indexOf(ch, i + 1); if (i < 0) { break; } s = s.substr(0, i) + repl + s.substr(i + 1); } return s; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.htmlEscape = function(s) { return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar( s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0'); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.getTextContent = function(elem) { if (elem == null) return ''; return elem.textContent || (typeof elem.textContent == 'undefined' ? elem.innerText : ''); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.setTextContent = function(elem, s) { // Updating the content of an element is an expensive operation. // Actually pays to first check whether the element is still unchanged. if (typeof elem.textContent == 'undefined') { if (elem.innerText != s) elem.innerText = s; } else { if (elem.textContent != s) elem.textContent = s; } }; /////////////////////////////////////////////////////////////////////////////// // Insert a blank line a position y. This method ignores the scrollback buffer // The caller has to add the length of the scrollback buffer to the position, // if necessary. // If the position is larger than the number of current lines, this method // just adds a new line right after the last existing one. It does not add // any missing lines in between. It is the caller's responsibility to do so. VT100.prototype.insertBlankLine = function(y, color, style) { if (!color) color = this.color; if (!style) style = ''; var line = document.createElement('div'); var span = document.createElement('span'); span.style.cssText = style; span.className = color; this.setTextContent(span, '\n'); line.appendChild(span); line.style.height = this.cursorHeight + 'px'; var crt = this.crt[this.currentScreen]; if (crt.childNodes.length > y) crt.insertBefore(line, crt.childNodes[y]); else crt.appendChild(line); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.updateWidth = function() { this.terminalWidth = Math.floor(this.crt[this.currentScreen].offsetWidth/ this.cursorWidth); return this.terminalWidth; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.updateHeight = function() { // We want to be able to display either a terminal window that fills the // entire browser window, or a terminal window that is contained in a //
which is embededded somewhere in the web page. if (this.isEmbedded) { // Embedded terminal. Use size of the containing
(id="vt100"). this.terminalHeight = Math.floor((this.container.clientHeight-1) / this.cursorHeight); } else { // Use the full browser window. this.terminalHeight = Math.floor(((window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight)-1)/ this.cursorHeight); } return this.terminalHeight; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.updateNumScrollbackLines = function() { var scrollback = Math.floor(this.crt[this.currentScreen].offsetHeight / this.cursorHeight) - this.terminalHeight; this.numScrollbackLines = scrollback < 0 ? 0 : scrollback; return this.numScrollbackLines; }; /////////////////////////////////////////////////////////////////////////////// // prune white-space from the end of the current line VT100.prototype.pruneWhiteSpace = function(line) { var span = line.lastChild; while (span) { // don't prune white-space with special attributes (e.g. inverse video) if (span.style.cssText.length || span.className.indexOf('invert') > 0 || span.className.indexOf('underline') > 0) return; // scan backwards looking for the first non-space character var s = this.getTextContent(span); for (var i = s.length; i--; ) { if (s.charAt(i) == ' ' || s.charAt(i) == '\u00A0') continue; if (i+1 != s.length) this.setTextContent(span, s.substr(0, i+1)); span = null; break; } if (span) { var sibling = span; span = span.previousSibling; // remove blank s from end of line line.removeChild(sibling); } } if (!line.firstChild) { // completely empty (blank) line var span = document.createElement('span'); span.style.cssText = ''; span.className = this.color; this.setTextContent(span, '\n'); line.appendChild(span); } }; /////////////////////////////////////////////////////////////////////////////// // prune white-space from the end of all lines (screen and scrollback) so that // blank lines and space-padded lines copy to clipboard more conservatively VT100.prototype.pruneAllWhiteSpace = function() { for (var line = this.crt[this.currentScreen].firstChild; line; line = line.nextSibling) this.pruneWhiteSpace(line); }; /////////////////////////////////////////////////////////////////////////////// // traverse all lines and truncate at |width| characters VT100.prototype.truncateLines = function(width) { if (width < 0) width = 0; for (var line = this.crt[this.currentScreen].firstChild; line; line = line.nextSibling) { var x = 0; for (var span = line.firstChild; span; span = span.nextSibling) { var s = this.getTextContent(span); var l = s.length; if (x + l > width) { this.setTextContent(span, s.substr(0, width - x)); while (span.nextSibling) line.removeChild(line.lastChild); break; } x += l; } } }; /////////////////////////////////////////////////////////////////////////////// // return the line represented by the |y| argument // see opening of putString() for some of the logic VT100.prototype.findLine = function(y) { var yIdx = y + this.numScrollbackLines; var crt = this.crt[this.currentScreen]; var line; // create missing blank lines at end of page while (crt.childNodes.length <= yIdx) this.insertBlankLine(yIdx); line = crt.childNodes[yIdx]; return line; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.putString = function(x, y, text, color, style) { if (!color) { if (this.color) color = this.color; else if (this.colorTheme) color = this.colorTheme; else color = ''; } if (!style) style = ''; var yIdx = y + this.numScrollbackLines; var line; var sibling; var s; var span; var xPos = 0; var crt = this.crt[this.currentScreen]; if (!text.length && (yIdx >= crt.childNodes.length || crt.childNodes[yIdx].tagName != 'DIV')) { // Positioning cursor to a blank location span = null; } else { // Create missing blank lines at end of page while (crt.childNodes.length <= yIdx) { // In order to simplify lookups, we want to make sure that each line // is represented by exactly one element (and possibly a whole bunch // of children). // For non-blank lines, we can create a
containing one or more // s. For blank lines, this fails as browsers tend to optimize // away. But fortunately, a
 tag containing a newline character
         // appears to work for all browsers (a   would also work, but
         // then copying from the browser window would insert superfluous
         // spaces into the clipboard).
         this.insertBlankLine(yIdx);
      }
      line = crt.childNodes[yIdx];

      // if necessary, promote an empty (blank) line to a line full of spaces
      span = line.firstChild;
      if (this.getTextContent(span) == '\n')
         this.setTextContent(span, this.spaces(this.terminalWidth));

      // Scan list of s until we find the one where our text starts
      span = line.firstChild;
      var len;
      while (span.nextSibling && xPos < x)
      {
         len = this.getTextContent(span).length;
         if (xPos + len > x) break;
         xPos += len;
         span = span.nextSibling;
      }

      if (text.length)
      {
         // If current  not long enough, pad with spaces or add new span
         s = this.getTextContent(span);
         var oldColor = span.className;
         var oldStyle = span.style.cssText;
         if (xPos + s.length < x)
         {
            if (oldColor != this.colorTheme || oldStyle.length)
            {
               // i.e. different color or any style
               span = document.createElement('span');
               line.appendChild(span);
               xPos += s.length;
               s = '';
            }
            do {
               s += ' ';
            } while (xPos + s.length < x);
            this.styleSpan(span, oldColor, oldStyle);
         }
 
         // if styles do not match, or all spaces, create a new 
         var del = text.length - s.length + x - xPos;
         if (oldColor != color || oldStyle != style ||
             this.getTextContent(span).trim() == '')
         {
            if (xPos == x)
            {
               // replacing text at beginning of existing 
               if (text.length >= s.length)
               {
                  // new text is equal or longer than existing text
                  s = text;
               }
               else
               {
                  // insert new  before the current one, then remove
                  // leading part of existing , adjust style of new
                  //  , and finally set its contents
                  sibling = document.createElement('span');
                  line.insertBefore(sibling, span);
                  this.setTextContent(span, s.substr(text.length));
                  this.styleSpan(span, oldColor, oldStyle);
                  span = sibling;
                  s = text;
               }
            }
            else
            {
               // replacing text some way into the existing 
               var remainder = s.substr(x + text.length - xPos);
               this.setTextContent(span, s.substr(0, x - xPos));
               this.styleSpan(span, oldColor, oldStyle);
               xPos = x;
               sibling = document.createElement('span');
               if (span.nextSibling)
               {
                  line.insertBefore(sibling, span.nextSibling);
                  span = sibling;
                  if (remainder.length)
                  {
                     sibling = document.createElement('span');
                     this.setTextContent(sibling, remainder);
                     this.styleSpan(sibling, oldColor, oldStyle);
                     line.insertBefore(sibling, span.nextSibling);
                  }
               }
               else
               {
                  line.appendChild(sibling);
                  span = sibling;
                  if (remainder.length)
                  {
                     sibling = document.createElement('span');
                     this.setTextContent(sibling, remainder);
                     this.styleSpan(sibling, oldColor, oldStyle);
                     line.appendChild(sibling);
                  }
               }
               s = text;
            }
         }
         else
         {
            // overwrite (partial)  with new text
            s = s.substr(0, x - xPos) + text + s.substr(x + text.length - xPos);
         }
         this.setTextContent(span, s);
         this.styleSpan(span, color, style);

         // delete all subsequent s that have just been overwritten
         sibling = span.nextSibling;
         while (del > 0 && sibling)
         {
            s = this.getTextContent(sibling);
            len = s.length;
            if (len <= del) {
               line.removeChild(sibling);
               del -= len;
               sibling = span.nextSibling;
            } else {
               this.setTextContent(sibling, s.substr(del));
               break;
            }
         }

         // merge sibling into span if styles are identical
         this.mergeIntoOne (line, span, sibling);
      }
   }

   // Position cursor
   this.cursorX = x + text.length;
   if (this.cursorX >= this.terminalWidth) {
      this.cursorX = this.terminalWidth - 1;
      if (this.cursorX < 0) {
         this.cursorX = 0;
     }
   }
   var pixelX = -1;
   var pixelY = -1;
   if (!this.cursor.style.visibility)
   {
      var idx = this.cursorX - xPos;
      if (span)
      {
         // If we are in a non-empty line, take the cursor Y position from
         // the other elements in this line. If dealing with broken,
         // non-proportional fonts, this is likely to yield better results.
         if (this.isEmbedded) {
            pixelY = span.offsetTop;
         } else {
            pixelY = span.offsetTop + span.offsetParent.offsetTop;
         }
         s = this.getTextContent(span);
         var nxtIdx = idx - s.length;
         if (nxtIdx < 0) {
            this.setTextContent(this.cursor, s.charAt(idx));
            pixelX = span.offsetLeft + idx*span.offsetWidth / s.length;
         } else {
            if (nxtIdx == 0) {
               pixelX = span.offsetLeft + span.offsetWidth;
            }
            if (span.nextSibling) {
               s = this.getTextContent(span.nextSibling);
               this.setTextContent(this.cursor, s.charAt(nxtIdx));
               if (pixelX < 0) {
                  pixelX = span.nextSibling.offsetLeft +
                           nxtIdx*span.offsetWidth / s.length;
               }
            } else {
               this.setTextContent(this.cursor, ' ');
            }
         }
      }
      else
      {
         this.setTextContent(this.cursor, ' ');
      }
   }

   if (pixelX >= 0) {
      this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))  + 'px';
   } else {
      this.setTextContent(this.space, this.spaces(this.cursorX));
      this.cursor.style.left = this.space.offsetWidth +
                               crt.offsetLeft + 'px';
   }
   this.cursorY = yIdx - this.numScrollbackLines;
   if (pixelY >= 0) {
      this.cursor.style.top = pixelY + 'px';
   } else {
      this.cursor.style.top = yIdx*this.cursorHeight +
                              crt.offsetTop + 'px';
   }

   if (text.length)
   {
      // merge into previous sibling if styles are identical
      if ((sibling = span.previousSibling))
         this.mergeIntoOne (line, sibling, span);
   }
};

///////////////////////////////////////////////////////////////////////////////

// merge two into one and delete two
// if colors and styles are identical and both are
// empty strings or both are non-empty strings

VT100.prototype.mergeIntoOne = function (line, one, two)
{
   if (!two) return;

   if (one.className == two.className &&
       one.style.cssText == two.style.cssText)
   {
      var none1 = (this.getTextContent(one).trim() == '');
      var none2 = (this.getTextContent(two).trim() == '');
      if ((none1 && none2) || (!none1 && !none2))
      {
         this.setTextContent(one,
                             this.getTextContent(one) +
                             this.getTextContent(two));
         line.removeChild(two);
      }
   }
}

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.styleSpan = function (span, color, style)
{
   if (this.getTextContent(span).trim() == '')
   {
      if (this.isInverted || this.flashInverted)
         span.className = this.colorThemeInvert;
      else
         span.className = this.colorTheme;
//      span.style.cssText = '';
      span.style.cssText = style;
   }
   else
   {
      span.className = color;
      span.style.cssText = style;
   }
}

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.gotoXY = function(x, y)
{
   if (x >= this.terminalWidth) {
      x = this.terminalWidth - 1;
   }
   if (x < 0) {
      x = 0;
   }

   var minY, maxY;
   if (this.offsetMode)
   {
      minY = this.top;
      maxY = this.bottom;
   }
   else
   {
      minY = 0;
      maxY = this.terminalHeight;
   }

   if (y >= maxY) {
      y = maxY - 1;
   }
   if (y < minY) {
      y = minY;
   }

   this.putString(x, y, '', undefined, undefined);
   this.needWrap = false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.gotoXaY = function(x, y)
{
   this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.refreshInvertedState = function()
{
   if (this.isInverted || this.flashInverted)
   {
      if (this.scrollable.className == this.colorThemeInvert) return;
      this.color = this.colorThemeInvert;
      this.scrollable.className = this.colorThemeInvert + ' invert';
   }
   else
   {
      if (this.scrollable.className == this.colorTheme) return;
      this.color = this.colorTheme;
      this.scrollable.className = this.colorTheme;
   }

   var tags = document.getElementsByTagName('span');
   for (var idx = 0; idx < tags.length; idx++)
      if (tags[idx].className.substr(0,4) == 'ansi')
         tags[idx].className = this.invertColor(tags[idx].className);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.flashScreen = function(tmo)
{
   if (!tmo)
   {
      if (this.flashInverted) return;
      this.flashInverted = true;
      setTimeout(function(vt100) {
                    return function()
                    {
                       vt100.flashScreen(true);
                    };
                 }(this), 100);
   }
   else
      this.flashInverted = false;

   this.refreshInvertedState();
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.enableAlternateScreen = function(state)
{
   // Don't do anything, if we are already on the desired screen
   if ((state ? 1 : 0) == this.currentScreen) {
      // Calling the resizer is not actually necessary. But it is a good way
      // of resetting state that might have gotten corrupted.
      this.resizer();
      return;
   }
  
   // We save the full state of the normal screen, when we switch away from it.
   // But for the alternate screen, no saving is necessary.
   // We always reset it when we switch to it.
   if (state) {
      this.saveCursor();
   }

   // Display new screen, and initialize state (the resizer does that for us).
   this.currentScreen = state ? 1 : 0;
   this.crt[1-this.currentScreen].style.display = 'none';
   this.crt[this.currentScreen].style.display   = '';
   this.resizer();

   // If we switched to the alternate screen, reset it completely.
   // Otherwise restore the saved state.
   if (state) {
      this.gotoXY(0, 0);
      this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
                       this.color, this.style);
   } else {
      this.restoreCursor();
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.hideCursor = function()
{
   var hidden = this.cursor.style.visibility == 'hidden';
   if (!hidden) {
     this.cursor.style.visibility = 'hidden';
     return true;
   }
   return false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.showCursor = function(x, y)
{
   if (this.cursor.style.visibility) {
      this.cursor.style.visibility = '';
      this.putString(x == undefined ? this.cursorX : x,
                     y == undefined ? this.cursorY : y,
                     '', undefined, undefined);
      return true;
   }
   return false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.scrollBack = function()
{
   var i = this.scrollable.scrollTop - this.scrollable.clientHeight;
   this.scrollable.scrollTop = i < 0 ? 0 : i;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.scrollFore = function()
{
   var i = this.scrollable.scrollTop + this.scrollable.clientHeight;
   this.scrollable.scrollTop = i > this.numScrollbackLines *
                                   this.cursorHeight + 1
                               ? this.numScrollbackLines *
                                 this.cursorHeight + 1
                               : i;
};

///////////////////////////////////////////////////////////////////////////////

// return a string containing the requested number of spaces

VT100.prototype.spaces = function(count)
{
   while (this.spacesBuffer.length < count) this.spacesBuffer += ' ';

   return this.spacesBuffer.substr(0,count);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.clearRegion = function(x, y, w, h, color, style)
{
   w += x;
   if (x < 0) {
      x = 0;
   }
   if (w > this.terminalWidth) {
      w = this.terminalWidth;
   }
   if ((w -= x) <= 0) {
      return;
   }
   h += y;
   if (y < 0) {
      y = 0;
   }
   if (h > this.terminalHeight) {
      h = this.terminalHeight;
   }
   if ((h -= y) <= 0) {
      return;
   }

   /* clearing the entire screen? */
   var cls = (w == this.terminalWidth && h == this.terminalHeight &&
              (color == undefined || color == this.color) && !style);

   // Special case the situation where we clear the entire screen, and we do
   // not have a scrollback buffer. In that case, we should just remove all
   // child nodes.
   if (cls && !this.numScrollbackLines)
   {
      var crt = this.crt[this.currentScreen];
      while (crt.lastChild) {
        crt.removeChild(crt.lastChild);
      }
      this.putString(this.cursorX, this.cursorY, '', undefined, undefined);
   }
   else
   {
      var hidden = this.hideCursor();
      var cx = this.cursorX;
      var cy = this.cursorY;
      var s = this.spaces(w);

      /* clearing (a) complete line(s)? */
      var cln = (w == this.terminalWidth &&
                 (color == undefined || color == this.color) && !style);

      for (var i = y+h; i-- > y; )
      {
         if (cln)
         {
            var line = this.findLine(i);
            if (line.DECstyle) line.DECstyle = line.style.cssText = undefined;
         }
         this.putString(x, i, s, color, style);
      }
      hidden ? this.showCursor(cx, cy) :
               this.putString(cx, cy, '', undefined, undefined);
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w)
{
   var text = [ ];
   var className = [ ];
   var style = [ ];
   var crt = this.crt[this.currentScreen];

   if (sY >= crt.childNodes.length)
   {
      text[0] = this.spaces(w);
      className[0] = undefined;
      style[0] = undefined;
   } else
   {
      var line = crt.childNodes[sY];
      if (line.tagName != 'DIV' || !line.childNodes.length)
      {
         text[0] = this.spaces(w);
         className[0] = undefined;
         style[0] = undefined;
      }
      else
      {
         var x = 0;
         for (var span = line.firstChild;
              span && w > 0;
              span = span.nextSibling)
         {
            var s = this.getTextContent(span);
            var len = s.length;
            if (x + len > sX)
            {
               var o = sX > x ? sX - x : 0;
               text[text.length] = s.substr(o, w);
               className[className.length] = span.className;
               style[style.length] = span.style.cssText;
               w -= len - o;
            }
            x += len;
         }
         if (w > 0)
         {
            text[text.length] = this.spaces(w);
            className[className.length]  = undefined;
            style[style.length] = undefined;
         }
      }
   }

   var hidden = this.hideCursor();
   var cx = this.cursorX;
   var cy = this.cursorY;
   for (var i = 0; i < text.length; i++)
   {
      var color;
      if (className[i]) {
         color = className[i];
      } else {
         color = this.color;
      }
      this.putString(dX, dY - this.numScrollbackLines,
                     text[i], color, style[i]);
      dX += text[i].length;
   }

   hidden ? this.showCursor(cx, cy) :
            this.putString(cx, cy, '', undefined, undefined);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY, color, style)
{
   var left  = incX < 0 ? -incX : 0;
   var right = incX > 0 ?  incX : 0;
   var up    = incY < 0 ? -incY : 0;
   var down  = incY > 0 ?  incY : 0;

   // Clip region against terminal size
   var dontScroll = null;
   w += x;
   if (x < left) {
      x = left;
   }
   if (w > this.terminalWidth - right) {
      w = this.terminalWidth - right;
   }
   if ((w -= x) <= 0) {
      dontScroll= 1;
   }
   h += y;
   if (y < up) {
      y = up;
   }
   if (h > this.terminalHeight - down) {
      h = this.terminalHeight - down;
   }
   if ((h -= y) < 0) {
      dontScroll = 1;
   }
   if (!dontScroll)
   {
      if (style && style.indexOf('underline'))
      {
         // Different terminal emulators disagree on the attributes that
         // are used for scrolling. The consensus seems to be, never to
         // fill with underlined spaces. N.B. this is different from the
         // cases when the user blanks a region. User-initiated blanking
         // always fills with all of the current attributes.
         style = style.replace(/text-decoration:underline;/, '');
      }

      // Compute current scroll position
      var scrollPos = this.numScrollbackLines -
                      (this.scrollable.scrollTop-1) / this.cursorHeight;

      // Determine original cursor position. Hide cursor temporarily to avoid
      // visual artifacts.
      var hidden = this.hideCursor();
      var cx = this.cursorX;
      var cy = this.cursorY;
      var crt = this.crt[this.currentScreen];

      if (!incX && !x && w == this.terminalWidth)
      {
         // Scrolling entire lines
         if (incY < 0)
         {
            // Scrolling up
            if (!this.currentScreen && y == -incY &&
                h == this.terminalHeight + incY)
            {
               // Scrolling up with adding to the scrollback buffer.
               // This is only possible if there are at least as many
               // lines in the crt as the terminal is high
               while (crt.childNodes.length < this.terminalHeight)
                  this.insertBlankLine(this.terminalHeight);
          
               // Add new lines at bottom in order to force scrolling
               for (var i = 0; i < y; i++)
                  this.insertBlankLine(crt.childNodes.length, color, style);

               // Adjust the number of lines in the scrollback buffer by
               // removing excess entries.
               this.updateNumScrollbackLines();
               while (this.numScrollbackLines >
                      (this.currentScreen ? 0 : this.maxScrollbackLines))
               {
                  crt.removeChild(crt.firstChild);
                  this.numScrollbackLines--;
               }

               // Mark lines in the scrollback buffer
               // so that they do not get printed.
               for (var i = this.numScrollbackLines, j = -incY;
                    i-- > 0 && j-- > 0; ) {
                  crt.childNodes[i].className = 'scrollback';
               }
            } else
            {
               // Scrolling up without adding to the scrollback buffer.
               for (var i = -incY;
                    i-- > 0 &&
                    crt.childNodes.length >
                    this.numScrollbackLines + y + incY; )
               {
                  crt.removeChild(
                     crt.childNodes[this.numScrollbackLines + y + incY]);
               }

               // If we used to have a scrollback buffer, we must make sure
               // that we add back blank lines at the bottom of the terminal.
               // Similarly, if we are scrolling in the middle of the screen,
               // we must add blank lines to ensure that the bottom of the
               // screen does not move up.
               if (this.numScrollbackLines > 0 ||
                   crt.childNodes.length >
                   this.numScrollbackLines+y+h+incY)
               {
                  for (var i = -incY; i-- > 0; )
                     this.insertBlankLine(this.numScrollbackLines + y + h + incY,
                                          color, style);
               }
            }
         } else
         {
            // Scrolling down
            for (var i = incY;
                 i-- > 0 &&
                 crt.childNodes.length > this.numScrollbackLines + y + h; ) 
               crt.removeChild(crt.childNodes[this.numScrollbackLines+y+h]);

            for (var i = incY; i--; )
               this.insertBlankLine(this.numScrollbackLines + y, color, style);
         }
      } else
      {
         // Scrolling partial lines
         if (incY <= 0)
         {
            // Scrolling up or horizontally within a line
            for (var i = y + this.numScrollbackLines;
                 i < y + this.numScrollbackLines + h;
                 i++) {
               this.copyLineSegment(x + incX, i + incY, x, i, w);
            }
         } else
         {
            // Scrolling down
            for (var i = y + this.numScrollbackLines + h;
                 i-- > y + this.numScrollbackLines; ) {
               this.copyLineSegment(x + incX, i + incY, x, i, w);
            }
         }

         // Clear blank regions
         if (incX > 0) {
            this.clearRegion(x, y, incX, h, color, style);
         } else
         if (incX < 0) {
            this.clearRegion(x + w + incX, y, -incX, h, color, style);
         }
         if (incY > 0) {
            this.clearRegion(x, y, w, incY, color, style);
         } else
         if (incY < 0) {
            this.clearRegion(x, y + h + incY, w, -incY, color, style);
         }
      }

      // Reset scroll position
      this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
                                  this.cursorHeight + 1;

      // Move cursor back to its original position
      hidden ? this.showCursor(cx, cy) :
               this.putString(cx, cy, '', undefined, undefined);
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.copy = function(selection)
{
   if (selection == undefined) {
      selection = this.selection();
   }
   this.internalClipboard = undefined;
   if (selection.length) {
      try {
         // IE
         this.cliphelper.value  = selection;
         this.cliphelper.select();
         this.cliphelper.createTextRange().execCommand('copy');
      } catch (e) {
         this.internalClipboard = selection;
      }
      this.cliphelper.value = '';
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.copyLast = function()
{
   // Opening the context menu can remove the selection. We try to prevent this
   // from happening, but that is not possible for all browsers. So, instead,
   // we compute the selection before showing the menu.
   this.copy(this.lastSelection);
};

///////////////////////////////////////////////////////////////////////////////

// copy to the system clipboard

VT100.prototype.copyClip = function(selection)
{
   if (selection == undefined) {
      selection = this.selection();
   }
   if (selection.length)
   {
      try {
         if (!document.execCommand('copy'))
            alert(DCLinaboxMessage['COCLIP']);
      } catch (e) {
         alert(DCLinaboxMessage['COCLIP']);
      }
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.pasteFnc = function()
{
   var clipboard = undefined;
   if (this.internalClipboard != undefined) {
      clipboard = this.internalClipboard;
   }
   else
   {
      try {
         this.cliphelper.value = '';
         this.cliphelper.createTextRange().execCommand('paste');
         clipboard = this.cliphelper.value;
      }
      catch (e) {
      }
   }
   this.cliphelper.value = '';
   if (clipboard && this.menu.style.visibility == 'hidden')
   {
      return function() {
         this.keysPressed('' + clipboard);
      };
   } else {
      return undefined;
   }
};

///////////////////////////////////////////////////////////////////////////////

// paste from the system clipboard (sorta)

VT100.prototype.pasteClip = function()
{
   try {
      if (window.clipboardData)
         clipboard = window.clipboardData.getData('Text');
      else
         clipboard = undefined;
   } catch(e) {
      clipboard = undefined;
   }

   if (clipboard == undefined)
   {
      if (vtpaste) return setTimeout (pasteFocus, 500);
      var clipboard = prompt(DCLinaboxMessage['PACLIP'],'');
      if (clipboard != undefined)
         return this.keysPressed('' + clipboard);
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleLoginPrompt = function()
{
   this.loginPrompt = !this.loginPrompt;
   terminalStatus();
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleUTF = function()
{
   this.utfEnabled = !this.utfEnabled;

   // We always persist the last value that the user selected.
   // Not necessarily the last value that a random program requested.
   this.utfPreferred = this.utfEnabled;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleBell = function()
{
   this.visualBell = !this.visualBell;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleVmsKeysPC = function()
{
   this.vmsKeysPC = !this.vmsKeysPC;
   if (this.vmsKeysPC) this.vmsKeysMac = false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleVmsKeysMac = function()
{
   this.vmsKeysMac = !this.vmsKeysMac;
   if (this.vmsKeysMac) this.vmsKeysPC = false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleAudibleBell = function()
{
   this.audibleBell = !this.audibleBell;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleDarkBackground = function()
{
   this.themeDark = true;
   this.themeLight = this.themeGreen = this.themeLocal = false;
   this.colorTheme = 'ansi7 bgAnsi0';
   this.colorThemeInvert = this.invertColor(this.colorTheme);
   this.color = this.colorTheme;
   this.scrollable.className = this.colorTheme;
   this.attr = (this.attr & ~0xff) | (this.attrTheme = 0x07);
   this.applyTheme();
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleLightBackground = function()
{
   this.themeLight = true;
   this.themeDark = this.themeGreen = this.themeLocal = false;
   this.colorTheme = 'ansi0 bgAnsi7';
   this.colorThemeInvert = this.invertColor(this.colorTheme);
   this.color = this.colorTheme;
   this.scrollable.className = this.colorTheme;
   this.attr = (this.attr & ~0xff) | (this.attrTheme = 0x70);
   this.applyTheme();
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleGreenScreen = function()
{
   this.themeGreen = true;
   this.themeLight = this.themeDark = this.themeLocal = false;
   this.colorTheme = 'ansi2 bgAnsi0';
   this.colorThemeInvert = this.invertColor(this.colorTheme);
   this.color = this.colorTheme;
   this.scrollable.className = this.colorTheme;
   this.attr = (this.attr & ~0xff) | (this.attrTheme = 0x02);
   this.applyTheme();
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleThemeLocal = function()
{
   this.themeLocal = true;
   this.themeLight = this.themeDark = this.themeGreen = false;
   this.colorTheme = 'ansi9 bgAnsi9';
   this.colorThemeInvert = this.invertColor(this.colorTheme);
   this.color = this.colorTheme;
   this.scrollable.className = this.colorTheme;
   this.attr = (this.attr & ~0xff) | (this.attrTheme = 0x99);
   this.applyTheme();
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleOtherOptions = function(enabled)
{
   if (enabled == undefined)
      this.otherOptions = !this.otherOptions;
   else
      this.otherOptions = enabled;

   if (!this.otherOptions)
   {
      // when other options is disabled do likewise to the other options
      this.toggleLogStream(false);
      this.setWriteDelay(0);
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.toggleLogStream = function(enabled)
{
   if (enabled == undefined)
      this.logStream = !this.logStream;
   else
      this.logStream = enabled;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.setWriteDelay = function(mSec)
{
   if (mSec != undefined)
   {
      if (mSec == this.writeDelay)
         this.writeDelay = 0;
      else
         this.writeDelay = mSec;
   }
   writeDelay (this.writeDelay)
};

VT100.prototype.toggleWriteDelay10 = function()
{
   this.setWriteDelay(10);
};

VT100.prototype.toggleWriteDelay100 = function()
{
   this.setWriteDelay(100);
};

VT100.prototype.toggleWriteDelay1000 = function()
{
   this.setWriteDelay(1000);
};

///////////////////////////////////////////////////////////////////////////////

// apply the toggled theme to all spans (text) in the terminal

VT100.prototype.applyTheme = function()
{
    var color, invert, tags;

    if (this.isInverted || this.flashInverted)
    {
       color = this.colorThemeInvert;
       invert = this.colorTheme;
    }
    else
    {
       color = this.colorTheme;
       invert = this.colorThemeInvert;
    }

    tags = document.getElementsByTagName('span');
    for (var idx = 0; idx < tags.length; idx++)
       if (tags[idx].className.substr(0,4) == 'ansi')
          if (tags[idx].className.indexOf('invert') > 0)
             tags[idx].className = invert + ' invert';
          else
             tags[idx].className = color;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.invertColor = function(color)
{
   var ansis = color.split(' ');
   return 'ansi' + ansis[1].substr(6,1) + ' bgAnsi' + ansis[0].substr(4,1);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.about = function()
{
   alert('DCLinabox ' + DCLinaboxVersion + '\n' +
         'Copyright \u00a9 ' + DCLinaboxCopyright + '\n' +
         'For more information: http://wasd.vsm.com.au/wasd/\n\n' +
         'VT100 Terminal Emulator ' + '2.10 (revision 186)' +
         '\nCopyright \u00a9 2008-2009 by Markus Gutschke\n' +
         'For more information: http://shellinabox.com\n');
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.hideContextMenu = function()
{
   this.menu.style.visibility = 'hidden';
   this.menu.style.top        = '-100px';
   this.menu.style.left       = '-100px';
   this.menu.style.width      = '0px';
   this.menu.style.height     = '0px';
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.showContextMenu = function(x, y)
{
   var html = '' +
       '';

   if (this.otherOptions)
      html += '' +
           '';

   this.menu.innerHTML = html;

   var popup = this.menu.firstChild;
   var menuentries = this.getChildById(popup, 'menuentries');
 
   // Determine menu entries that should be disabled
   this.lastSelection = this.selection();
   if (!this.lastSelection.length) {
      menuentries.childNodes[0].className  = 'disabled';
      menuentries.childNodes[2].className  = 'disabled';
   }
   var p = this.pasteFnc();
   if (!p) menuentries.childNodes[1].className  = 'disabled';

   // Actions for default items
   var actions = [ this.copyLast, p,
                   this.copyClip, this.pasteClip,
                   this.reset,
                   this.toggleUTF,
                   this.toggleAudibleBell,
                   this.toggleBell,
                   this.toggleLoginPrompt,
                   this.toggleVmsKeysPC,
                   this.toggleVmsKeysMac,
                   this.toggleLightBackground,
                   this.toggleDarkBackground,
                   this.toggleGreenScreen ];

   if (this.themeLocalName)
      actions[actions.length] = this.toggleThemeLocal;

   actions[actions.length] = this.about;
   actions[actions.length] = this.toggleOtherOptions;

   actions[actions.length] = this.toggleLogStream;
   actions[actions.length] = this.toggleWriteDelay10;
   actions[actions.length] = this.toggleWriteDelay100;
   actions[actions.length] = this.toggleWriteDelay1000;

   for (var cnt = 2; cnt > 0; cnt--)
   {
      if (cnt == 2)
         var offset = this.activateContextMenu(menuentries.firstChild,
                                               actions, 0);
      else
         this.activateContextMenu(menuother.firstChild, actions, offset); 
   }

   // Position menu next to the mouse pointer
   if (x + popup.clientWidth > this.container.offsetWidth) {
      x = this.container.offsetWidth - popup.clientWidth;
   }
   if (x < 0) {
      x = 0;
   }
   if (y + popup.clientHeight > this.container.offsetHeight) {
      y = this.container.offsetHeight-popup.clientHeight;
   }
   if (y < 0) {
      y = 0;
   }
   popup.style.left = x + 'px';
   popup.style.top  = y + 'px';

   // Block all other interactions with the terminal emulator
   this.menu.style.left = '0px';
   this.menu.style.top = '0px';
   this.menu.style.width = this.container.offsetWidth  + 'px';
   this.menu.style.height = this.container.offsetHeight + 'px';

   this.addListener(this.menu, 'click', function(vt100)
                                        {
                                           return function()
                                           {
                                              vt100.hideContextMenu();
                                           };
                                        }(this));

   // Show the menu
   this.menu.style.visibility  = '';
};

///////////////////////////////////////////////////////////////////////////////

// hook up event listeners to the menu items

VT100.prototype.activateContextMenu = function(node, actions, offset)
{
   for (var cnt = 0; node; node = node.nextSibling)
   {
      if (node.tagName == 'LI')
      {
         if (node.className != 'disabled')
         {
             this.addListener(node, 'mouseover',
                              function(vt100, node)
                              {
                                 return function()
                                 {
                                    node.className = 'hover';
                                 };
                              }(this, node));

             this.addListener(node, 'mouseout',
                              function(vt100, node)
                              {
                                 return function()
                                 {
                                    node.className = '';
                                 };
                              }(this, node));

             this.addListener(node, 'mousedown',
                              function(vt100, action)
                              {
                                 return function(event)
                                 {
                                    vt100.hideContextMenu();
                                    action.call(vt100);
                                    vt100.storeUserSettings();
                                    return vt100.cancelEvent(event ||
                                                             window.event);
                                 };
                              }(this, actions[cnt + offset]));

             this.addListener(node, 'mouseup',
                              function(vt100)
                              {
                                 return function(event)
                                 {
                                    return vt100.cancelEvent(event ||
                                                             window.event);
                                 };
                              }(this));

             this.addListener(node, 'mouseclick',
                              function(vt100)
                              {
                                 return function(event)
                                 {
                                    return vt100.cancelEvent(event ||
                                                             window.event);
                                 };
                              }());

            node.className = '';
         }

         cnt++;
      }
   }

   return cnt;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.keysPressed = function(ch)
{
   for (var i = 0; i < ch.length; i++)
   {
      var c = ch.charCodeAt(i);
      this.vt100(c >= 7 && c <= 15 ||
                 c == 24 || c == 26 || c == 27 || c >= 32
                 ? String.fromCharCode(c) : '<' + c + '>');
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.applyModifiers = function(ch, event)
{
   if (ch)
   {
      if (event.ctrlKey)
      {
         if (ch >= 32 && ch <= 127)
         {
            // For historic reasons, some control characters are "special"
            switch (ch)
            {
               case /* 3 */ 51: ch  =  27; break;
               case /* 4 */ 52: ch  =  28; break;
               case /* 5 */ 53: ch  =  29; break;
               case /* 6 */ 54: ch  =  30; break;
               case /* 7 */ 55: ch  =  31; break;
               case /* 8 */ 56: ch  = 127; break;
               case /* ? */ 63: ch  = 127; break;
               default:         ch &=  31; break;
            }
         }
      }
      return String.fromCharCode(ch);
   }
   else
   {
      return undefined;
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.handleKey = function(event)
{
   var ch, key, special;

   if (typeof event.charCode != 'undefined')
   {
      // non-IE keypress events have a translated charCode value. Also, our
      // fake events generated when receiving keydown events include this data
      // on all browsers.
      ch = event.charCode;
      key = event.keyCode;
   }
   else
   {
      // When sending a keypress event, IE includes the translated character
      // code in the keyCode field.
      ch = event.keyCode;
      key = undefined;
   }

   if (ch == 0)
      special = true;
   else
   if (typeof event.location != 'undefined') {
      if (event.location == 3)
         special = true;
      else
         special = false;
   }
   else
   if (typeof event.which != 'undefined')  /* event.which is deprecated */
      special = (event.which == 0);
   else
      special = false;

   if (special && (this.vmsKeysMac || this.vmsKeysPC))
   {
      // VMS-specific numeric and editing key mappings
      // http://redgrittybrick.org/keyboards.html
      // handles Mac extended keyboard on Mac, PC keyboard on Mac,
      // and PC keyboard under Windows (VM tested under 7 and 10)
      var hit = undefined;

      if (this.applKeyMode)
      {
         // application mode numeric keyboard
         if (this.vmsKeysMac)
         {
            // Mac layout
            switch (ch)
            {
               case 0x2a : /* "*" */ hit = '\u001bOS'; break; /* PF4 */
               case 0x2b : /* "," */ hit = '\u001bOl'; break;
               case 0x2d : /* "-" */ hit = '\u001bOm'; break;
               case 0x2e : /* "." */ hit = '\u001bOn'; break;
               case 0x2f : /* "/" */ hit = '\u001bOR'; break; /* PF3 */
               case 0x3d : /* "=" */ hit = '\u001bOQ'; break; /* PF2 */
            }
            if (!hit)
            {
               switch (key)
               {
                  case 0x0c : /* clear */ hit = '\u001bOP'; /* PF1 */ break;
                  case 0x0d : /* enter */ hit = '\u001bOM'; break;
               }
            }
         }
         else
         {
            // PC layout
            switch (ch)
            {
               case 0x2a : /* "*" */ hit = '\u001bOR'; /* PF3 */ break;
               case 0x2b : /* "+" */
                  if (event.ctrlKey)
                     hit = '\u001bOm'; /* Firefox */
                  else
                     hit = '\u001bOl';
                  break;
               case 0x2d : /* "-" */
                  if (event.ctrlKey)
                     hit = '\u001bOm'; /* plus key with meta or alt */
                  else
                     hit = '\u001bOS'; /* PF4 */
                  break;
               case 0x2e : /* "." */ hit = '\u001bOn'; break;
               case 0x2f : /* "/" */ hit = '\u001bOQ'; /* PF2 */ break;
            }
            if (!hit)
            {
               switch (key)
               {
                  // too confusing with numlock action - use ctrl-7 or ctrl-/
                  // case 0x0c : /* numlock */ hit = '\u001bOP'; /* PF1 */ break;
                  // case 0x90 : /* numlock */ hit = '\u001bOP'; break; /* MSIE11 */

                  /* dance around numlock under Windows */
                  case 0x67 : /* "ctrl-7" */
                  case 0x6f : /* "ctrl-/" */
                     if (event.ctrlKey) hit = '\u001bOP'; /* PF1 */ break;

                  case 0x0d : /* enter */ hit = '\u001bOM'; break;
                  case 0x6b : /* "-" */ hit = '\u001bOm'; /* Chrome */ break;
               }
            }
         }

         if (!hit)
         {
            // common numeric keys
            switch (ch)
            {
               case 0x30 : hit = '\u001bOp'; break;
               case 0x31 : hit = '\u001bOq'; break;
               case 0x32 : hit = '\u001bOr'; break;
               case 0x33 : hit = '\u001bOs'; break;
               case 0x34 : hit = '\u001bOt'; break;
               case 0x35 : hit = '\u001bOu'; break;
               case 0x36 : hit = '\u001bOv'; break;
               case 0x37 : hit = '\u001bOw'; break;
               case 0x38 : hit = '\u001bOx'; break;
               case 0x39 : hit = '\u001bOy'; break;
            }
         }
      }
      else
      {
         // numeric mode numeric keyboard
         if (this.vmsKeysMac)
         {
            // Mac layout specific keys
            switch (ch)
            {
               case 0x2a : /* "*" */ hit = '\u001bOS'; /* PF4 */ break;
               case 0x2b : hit = ','; break;
               case 0x2d : hit = '-'; break;
               case 0x2e : hit = '.'; break;
               case 0x2f : /* "/" */ hit = '\u001bOR'; /* PF3 */ break;
               case 0x3d : /* "=" */ hit = '\u001bOQ'; /* PF2 */ break;
            }
            if (!hit)
            {
               switch (key)
               {
                  case 0x0c : /* clear */ hit = '\u001bOP'; /* PF1 */ break;
                  case 0x0d : /* enter */ hit = '\u000d'; break;
               }
            }
         }
         else
         {
            // PC layout specific keys
            switch (ch)
            {
               case 0x2a : /* "*" */ hit = '\u001bOR'; /* PF3 */ break;
               case 0x2b : /* "+" */
                  if (event.ctrlKey)
                     hit = '-'; /* Firefox */
                  else
                     hit = ',';
                  break;
               case 0x2d : /* "-" */
                  if (event.ctrlKey)
                     hit = '-';
                  else
                     hit = '\u001bOS'; /* PF4 */
                  break;
               case 0x2e : hit = '.'; break;
               case 0x2f : /* "/" */ hit = '\u001bOQ'; /* PF2 */ break;

            }
            if (!hit)
            {
               switch (key)
               {
                  // too confusing with numlock action - use ctrl-7 or ctrl-/
                  // case 0x0c : /* numlock */ hit = '\u001bOP'; /* PF1 */ break;
                  // case 0x90 : /* numlock */ hit = '\u001bOP'; break; /* MSIE11 */

                  /* dance around numlock under Windows */
                  case 0x67 : /* "ctrl-7" */
                  case 0x6f : /* "ctrl-/" */
                     if (event.ctrlKey) hit = '\u001bOP'; /* PF1 */ break;

                  case 0x0d : /* enter */ hit = '\u000d'; break;
                  case 0x6b : /* "-" */ hit = '-'; /* Chrome */ break;
               }
            }
         }

         if (!hit)
         {
            // common numeric keys
            switch (ch)
            {
               case 0x0d : hit = '\u000d'; break;
               case 0x60 : hit = '0'; break;
               case 0x61 : hit = '1'; break;
               case 0x62 : hit = '2'; break;
               case 0x63 : hit = '3'; break;
               case 0x64 : hit = '4'; break;
               case 0x65 : hit = '5'; break;
               case 0x66 : hit = '6'; break;
               case 0x67 : hit = '7'; break;
               case 0x68 : hit = '8'; break;
               case 0x69 : hit = '9'; break;
            }
         }
      }

      if (!hit)
      {
         // editing keypad keys
         if (this.vmsKeysMac)
         {
            // Mac keys
            switch (key)
            {
               case 0x03 : hit = '\u001b[29~'; break; /* ctrl-lock -> Do */
               case 0x21 : hit = '\u001b[3~'; break;  /* page-up -> Remove */
               case 0x22 : hit = '\u001b[6~'; break;  /* down -> Next Screen */
               case 0x23 : hit = '\u001b[5~'; break;  /* up -> Prev Screen */

               case 0x24 : /* home -> Insert Here */ 
                  if (event.ctrlKey)
                     hit = '\u001b[1~'; /* with ctrl doubles as Find */
                  else
                     hit = '\u001b[2~';
                  break;

               case 0x2c : /* Firefox */
               case 0x7c : /* Chrome */ hit = '\u001b[28~'; break;

               case 0x2e : /* delete -> Select */
                  if (event.ctrlKey)
                     hit = '\u001b[1~'; /* with ctrl doubles as Find */
                  else
                     hit = '\u001b[4~';
                  break;

               case 0x91 : /* Firefox */
               case 0x7d : /* Chrome */
                  hit = '\u001b[29~'; break;  /* F14 -> DO */

               case 0x13 : /* Firefox */
               case 0x7e : /* Chrome */
                  hit = '\u001b[29~'; break;  /* F15 -> DO */
            }
         }
         else
         {
            // PC keys
            switch (key)
            {
               case 0x21 : /* page-up -> Remove */
                  if (event.ctrlKey)
                     hit = '\u001b[29~'; /* with ctrl doubles as Do */
                  else
                     hit = '\u001b[3~';
                  break;

               case 0x22 : hit = '\u001b[6~'; break;   /* down -> Next Screen */
               case 0x23 : hit = '\u001b[5~'; break;   /* up -> Prev Screen */

               case 0x24 : /* home -> Insert Here */ 
                  if (event.ctrlKey)
                     hit = '\u001b[29~'; /* with ctrl doubles as Do */
                  else
                     hit = '\u001b[2~';
                  break;

               case 0x2c : /* Firefox */
               case 0x7c : /* Chrome */ hit = '\u001b[28~'; break;

               case 0x2d : /* insert -> Find */
                  if (event.ctrlKey)
                     hit = '\u001b[28~'; /* with ctrl doubles as Help */
                  else
                     hit = '\u001b[1~';
                  break;

               case 0x2e : /* del -> Select */ hit = '\u001b[4~'; break;
               case 0x2f : /* insert -> Find */ hit = '\u001b[4~'; break;   
            }
         }
      }

      if (hit)
      {
         if (this.menu.style.visibility == 'hidden') this.keysPressed(hit);
         return;
      }
   }

   // Apply modifier keys (ctrl and shift)
   if (ch) {
      key = undefined;
   }
   ch = this.applyModifiers(ch, event);

   // By this point, "ch" is either defined and contains the character code, or
   // it is undefined and "key" defines the code of a function key 
   if (ch != undefined)
   {
      this.scrollable.scrollTop = this.numScrollbackLines *
                                  this.cursorHeight + 1;
   }
   else
   {
      if ((event.altKey || event.metaKey) &&
          !event.shiftKey && !event.ctrlKey)
      {
         // Many programs have difficulties dealing with parametrized escape
         // sequences for function keys. Thus, if ALT is the only modifier
         // key, return Emacs-style keycodes for commonly used keys.
         switch (key)
         {
            case  33: /* Page Up   */ ch = '\u001B<'; break;
            case  34: /* Page Down */ ch = '\u001B>'; break;
            case  37: /* Left      */ ch = '\u001Bb'; break;
            case  38: /* Up        */ ch = '\u001Bp'; break;
            case  39: /* Right     */ ch = '\u001Bf'; break;
            case  40: /* Down      */ ch = '\u001Bn'; break;
            case  46: /* Delete    */ ch = '\u001Bd';  break;
            default: break;
         }
      }
      else
      if (event.shiftKey && !event.ctrlKey &&
          !event.altKey && !event.metaKey)
      {
         switch (key)
         {
            case  33: /* Page Up   */ this.scrollBack(); return;
            case  34: /* Page Down */ this.scrollFore(); return;
            default: break;
         }
      }

      if (ch == undefined)
      {
         switch (key)
         {
            case   8: /* Backspace  */ ch = '\u007f'; break;
            case   9: /* Tab        */ ch = '\u0009'; break;
            case  10: /* Return     */ ch = '\u000A'; break;
            case  13: /* Enter      */ ch = this.crLfMode ?
                                              '\r\n' : '\r'; break;
            case  16: /* Shift      */ return;
            case  17: /* Ctrl       */ return;
            case  18: /* Alt        */ return;
            case  19: /* Break      */ return;
            case  20: /* Caps Lock  */ return;
            case  27: /* 'Escape     */ ch = '\u001B'; break;
            case  33: /* Page Up    */ ch = '\u001B[5~'; break;
            case  34: /* Page Down  */ ch = '\u001B[6~'; break;
            case  35: /* End        */ ch = '\u001BOF'; break;
            case  36: /* Home       */ ch = '\u001BOH'; break;
            case  37: /* Left       */ ch = this.cursorKeyMode ?
                                            '\u001BOD' : '\u001B[D'; break;
            case  38: /* Up         */ ch = this.cursorKeyMode ?
                                              '\u001BOA' : '\u001B[A'; break;
            case  39: /* Right      */ ch = this.cursorKeyMode ?
                                            '\u001BOC' : '\u001B[C'; break;
            case  40: /* Down       */ ch = this.cursorKeyMode ?
                                            '\u001BOB' : '\u001B[B'; break;
            case  45: /* Insert     */ ch = '\u001B[2~'; break;
            case  46: /* Delete     */ ch = '\u001B[3~'; break;
            case  91: /* Left Window  */ return;
            case  92: /* Right Window */ return;
            case  93: /* Select     */ return;
            case  96: /* 0    */ ch = this.applyModifiers(48, event); break;
            case  97: /* 1    */ ch = this.applyModifiers(49, event); break;
            case  98: /* 2    */ ch = this.applyModifiers(50, event); break;
            case  99: /* 3    */ ch = this.applyModifiers(51, event); break;
            case 100: /* 4    */ ch = this.applyModifiers(52, event); break;
            case 101: /* 5    */ ch = this.applyModifiers(53, event); break;
            case 102: /* 6    */ ch = this.applyModifiers(54, event); break;
            case 103: /* 7    */ ch = this.applyModifiers(55, event); break;
            case 104: /* 8    */ ch = this.applyModifiers(56, event); break;
            case 105: /* 9    */ ch = this.applyModifiers(58, event); break;
            case 106: /* *    */ ch = this.applyModifiers(42, event); break;
            case 107: /* +    */ ch = this.applyModifiers(43, event); break;
            case 109: /* -    */ ch = this.applyModifiers(45, event); break;
            case 110: /* .    */ ch = this.applyModifiers(46, event); break;
            case 111: /* /    */ ch = this.applyModifiers(47, event); break;
            case 112: /* F1   */ ch = '\u001BOP'; break;
            case 113: /* F2   */ ch = '\u001BOQ'; break;
            case 114: /* F3   */ ch = '\u001BOR'; break;
            case 115: /* F4   */ ch = '\u001BOS'; break;
            case 116: /* F5   */ ch = '\u001B[15~'; break;
            case 117: /* F6   */ ch = '\u001B[17~'; break;
            case 118: /* F7   */ ch = '\u001B[18~'; break;
            case 119: /* F8   */ ch = '\u001B[19~'; break;
            case 120: /* F9   */ ch = '\u001B[20~'; break;
            case 121: /* F10  */ ch = '\u001B[21~'; break;
            case 122: /* F11  */ ch = '\u001B[23~'; break;
            case 123: /* F12  */ ch = '\u001B[24~'; break;
            case 127: /* F16  */ ch = '\u001B[27~'; break;
            case 128: /* F17  */ ch = '\u001B[28~'; break;
            case 129: /* F18  */ ch = '\u001B[29~'; break;
            case 130: /* F19  */ ch = '\u001B[30~'; break;
            case 144: /* Num Lock     */ return;
            case 145: /* Scroll Lock  */ return;
            case 186: /* ;    */ ch = this.applyModifiers(59, event); break;
            case 187: /* =    */ ch = this.applyModifiers(61, event); break;
            case 188: /* ,    */ ch = this.applyModifiers(44, event); break;
            case 189: /* -    */ ch = this.applyModifiers(45, event); break;
            case 190: /* .    */ ch = this.applyModifiers(46, event); break;
            case 191: /* /    */ ch = this.applyModifiers(47, event); break;
            case 192: /* `    */ ch = this.applyModifiers(96, event); break;
            case 219: /* [    */ ch = this.applyModifiers(91, event); break;
            case 220: /* \    */ ch = this.applyModifiers(92, event); break;
            case 221: /* ]    */ ch = this.applyModifiers(93, event); break;
            case 222: /* '    */ ch = this.applyModifiers(39, event); break;
            default: return;
         }
         this.scrollable.scrollTop = this.numScrollbackLines *
                                     this.cursorHeight + 1;
      }
   }

   // "ch" now contains the sequence of keycodes to send.
   // But we might still have to apply the effects of modifier keys.

   if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
   {
      var start, digit, part1, part2;
      if ((start = ch.substr(0, 2)) == '\u001B[')
      {
         for (part1 = start;
              part1.length < ch.length &&
                (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; )
         {
            part1 = ch.substr(0, part1.length + 1);
         }
         part2 = ch.substr(part1.length);
         if (part1.length > 2) {
            part1 += ';';
         }
      }
      else
      if (start == '\u001BO')
      {
         part1 = start;
         part2 = ch.substr(2);
      }
      if (part1 != undefined)
      {
         ch = part1 +
              ((event.shiftKey ? 1 : 0) +
               (event.altKey|event.metaKey ? 2 : 0) +
               (event.ctrlKey ? 4 : 0)) +
              part2;
      }
      else
      if (ch.length == 1 && (event.altKey || event.metaKey))
      {
         ch = '\u001B' + ch;
      }
   }

   if (this.menu.style.visibility == 'hidden')
   {
      this.keysPressed(ch);
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.checkComposedKeys = function(event)
{
   // Composed keys (at least on Linux) do not generate normal events.
   // Instead, they get entered into the text field. We normally catch
   // this on the next keyup event.

   var s = this.input.value;
   if (s.length) {
      this.input.value = '';
      if (this.menu.style.visibility == 'hidden') {
         this.keysPressed(s);
      }
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.fixEvent = function(event)
{
   // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
   // is used as a second-level selector, clear the modifier bits before
   // handling the event.

   if (event.ctrlKey && event.altKey)
   {
      var fake = [ ];
      fake.charCode = event.charCode;
      fake.keyCode = event.keyCode;
      fake.ctrlKey = false;
      fake.shiftKey = event.shiftKey;
      fake.altKey = false;
      fake.metaKey = event.metaKey;
      fake.location = event.location;
      fake.which = event.which;
      return fake;
   }

   // Some browsers fail to translate keys, if both shift and alt/meta is
   // pressed at the same time. We try to translate those cases, but that
   // only works for US keyboard layouts.

   if (event.shiftKey)
   {
      var u = undefined;
      var s = undefined;
      switch (this.lastNormalKeyDownEvent.keyCode)
      {
         case  39: /* ' -> " */ u = 39; s =  34; break;
         case  44: /* , -> < */ u = 44; s =  60; break;
         case  45: /* - -> _ */ u = 45; s =  95; break;
         case  46: /* . -> > */ u = 46; s =  62; break;
         case  47: /* / -> ? */ u = 47; s =  63; break;

         case  48: /* 0 -> ) */ u = 48; s =  41; break;
         case  49: /* 1 -> ! */ u = 49; s =  33; break;
         case  50: /* 2 -> @ */ u = 50; s =  64; break;
         case  51: /* 3 -> # */ u = 51; s =  35; break;
         case  52: /* 4 -> $ */ u = 52; s =  36; break;
         case  53: /* 5 -> % */ u = 53; s =  37; break;
         case  54: /* 6 -> ^ */ u = 54; s =  94; break;
         case  55: /* 7 -> & */ u = 55; s =  38; break;
         case  56: /* 8 -> * */ u = 56; s =  42; break;
         case  57: /* 9 -> ( */ u = 57; s =  40; break;

         case  59: /* ; -> : */ u = 59; s =  58; break;
         case  61: /* = -> + */ u = 61; s =  43; break;
         case  91: /* [ -> { */ u = 91; s = 123; break;
         case  92: /* \ -> | */ u = 92; s = 124; break;
         case  93: /* ] -> } */ u = 93; s = 125; break; 
         case  96: /* ` -> ~ */ u = 96; s = 126; break;

         case 109: /* - -> _ */ u = 45; s =  95; break;
         case 111: /* / -> ? */ u = 47; s =  63; break;

         case 186: /* ; -> : */ u = 59; s =  58; break;
         case 187: /* = -> + */ u = 61; s =  43; break;
         case 188: /* , -> < */ u = 44; s =  60; break;
         case 189: /* - -> _ */ u = 45; s =  95; break;
         case 190: /* . -> > */ u = 46; s =  62; break;
         case 191: /* / -> ? */ u = 47; s =  63; break;
         case 192: /* ` -> ~ */ u = 96; s = 126; break;
         case 219: /* [ -> { */ u = 91; s = 123; break;
         case 220: /* \ -> | */ u = 92; s = 124; break;
         case 221: /* ] -> } */ u = 93; s = 125; break; 
         case 222: /* ' -> " */ u = 39; s =  34; break;

         default: break;
      }

      if (s && (event.charCode == u || event.charCode == 0))
      {
         var fake = [ ];
         fake.charCode = s;
         fake.keyCode = event.keyCode;
         fake.ctrlKey = event.ctrlKey;
         fake.shiftKey = event.shiftKey;
         fake.altKey = event.altKey;
         fake.metaKey = event.metaKey;
         fake.location = event.location;
         fake.which = event.which;
         return fake;
      }
   }

   return event;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.keyDown = function(event)
{
   this.checkComposedKeys(event);
   this.lastKeyPressedEvent = undefined;
   this.lastKeyDownEvent = undefined;
   this.lastNormalKeyDownEvent = event;

   var asciiKey = (event.keyCode == 32 ||
                   event.keyCode >= 48 && event.keyCode <= 57 ||
                   event.keyCode >= 65 && event.keyCode <= 90);
   var alphNumKey = (asciiKey ||
                     event.keyCode >=  96 && event.keyCode <= 105 ||
                     event.keyCode == 226);
   var normalKey = (alphNumKey ||
                    event.keyCode >=  59 || event.keyCode <=  64 ||
                    event.keyCode == 106 || event.keyCode == 107 ||
                    event.keyCode >= 109 && event.keyCode <= 111 ||
                    event.keyCode >= 112 && event.keyCode <= 123 ||
                    event.keyCode >= 160 && event.keyCode <= 192 ||
                    event.keyCode >= 219 && event.keyCode <= 222 ||
                    event.keyCode == 252);
   if ((event.keyCode >= 112 && event.keyCode <= 123) ||
       (event.keyCode >= 127 && event.keyCode <= 130))
   {
      // F1..F12 then F16..F20
      event.stopPropagation();
      event.preventDefault();
   }

   try
   {
      if (navigator.appName == 'Konqueror') {
         normalKey |= event.keyCode < 128;
      }
   }
   catch (e) { }

   // We normally prefer to look at keypress events, as they perform the
   // translation from keyCode to charCode. This is important, as the
   // translation is locale-dependent.
   // But for some keys, we must intercept them during the keydown event,
   // as they would otherwise get interpreted by the browser.
   // Even, when doing all of this, there are some keys that we can never
   // intercept. This applies to some of the menu navigation keys in IE.
   // In fact, we see them, but we cannot stop IE from seeing them, too.

   if ((event.charCode || event.keyCode) &&
       ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
        !event.shiftKey &&
        // Some browsers signal AltGR as both CTRL and ALT. Do not try to
        // interpret this sequence ourselves, as some keyboard layouts use
        // it for second-level layouts.
        !(event.ctrlKey && event.altKey)) ||
       this.catchModifiersEarly && normalKey && !alphNumKey &&
       (event.ctrlKey || event.altKey || event.metaKey) ||
       !normalKey))
   {
      this.lastKeyDownEvent = event;
      var fake = [ ];
      fake.ctrlKey = event.ctrlKey;
      fake.shiftKey = event.shiftKey;
      fake.altKey = event.altKey;
      fake.metaKey = event.metaKey;
      fake.location = event.location;
      fake.which = event.which;
      if (asciiKey)
      {
         fake.charCode = event.keyCode;
         fake.keyCode = 0;
      }
      else
      {
         fake.charCode = 0;
         fake.keyCode = event.keyCode;
         if (!alphNumKey && event.shiftKey) {
           fake = this.fixEvent(fake);
         }
      }

      this.handleKey(fake);
      this.lastNormalKeyDownEvent = undefined;

      try
      {
         // For non-IE browsers
         event.stopPropagation();
         event.preventDefault();
      }
      catch (e) { }

      try
      {
         // For IE
         event.cancelBubble = true;
         event.returnValue  = false;
      } 
      catch (e) { }

      return false;
   }

   // tab (for Chrome!) 
   if (event.keyCode == 9) {
      event.preventDefault();
   }
   // tab (for Windows!) 
   if (event.keyCode == 144) {
      event.preventDefault();
   }

   return true;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.keyPressed = function(event)
{
   if (this.lastKeyDownEvent)
   {
      // If we already processed the key on keydown, do not process it
      // again here. Ideally, the browser should not even have generated a
      // keypress event in this case. But that does not appear to always work.
      this.lastKeyDownEvent = undefined;
   }
   else
   {
      this.handleKey(event.altKey || event.metaKey
                     ? this.fixEvent(event) : event);
   }

   try
   {
      // For non-IE browsers
      event.preventDefault();
   }
   catch (e) { }

   try
   {
      // For IE
      event.cancelBubble = true;
      event.returnValue  = false;
   }
   catch (e) { }

   this.lastNormalKeyDownEvent = undefined;
   this.lastKeyPressedEvent    = event;

   return false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.keyUp = function(event)
{
   if (this.lastKeyPressedEvent)
   {
      // The compose key on Linux occasionally confuses the browser and keeps
      // inserting bogus characters into the input field, even if just regular
      // key has been pressed. Detect this case and drop the bogus characters.
      (event.target || event.srcElement).value = '';
   } 
   else
   {
      // This is usually were we notice that a key has been composed and
      // thus failed to generate normal events.
      this.checkComposedKeys(event);

      // Some browsers don't report keypress events if ctrl or alt is pressed
      // for non-alphanumerical keys. Patch things up for now, but in the
      // future we will catch these keys earlier (in the keydown handler).

      if (this.lastNormalKeyDownEvent)
      {
         // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
         this.catchModifiersEarly = true;
         var asciiKey = (event.keyCode ==  32 ||
                         event.keyCode >=  48 && event.keyCode <=  57 ||
                         event.keyCode >=  65 && event.keyCode <=  90);
         var alphNumKey = (asciiKey ||
                           event.keyCode >=  96 && event.keyCode <= 105);
         var normalKey = (alphNumKey ||
                          event.keyCode ==  59 || event.keyCode ==  61 ||
                          event.keyCode == 106 || event.keyCode == 107 ||
                          event.keyCode >= 109 && event.keyCode <= 111 ||
                          event.keyCode >= 112 && event.keyCode <= 123 ||
                          event.keyCode >= 186 && event.keyCode <= 192 ||
                          event.keyCode >= 219 && event.keyCode <= 222 ||
                          event.keyCode == 252);
         if ((event.keyCode >= 112 && event.keyCode <= 123) ||
             (event.keyCode >= 127 && event.keyCode <= 130))
         {
            // F1..F12
            event.stopPropagation();
            event.preventDefault();
         }
         var fake = [ ];
         fake.ctrlKey = event.ctrlKey;
         fake.shiftKey = event.shiftKey;
         fake.altKey = event.altKey;
         fake.metaKey = event.metaKey;
         fake.location = event.location;
         fake.which = event.which;
         if (asciiKey)
         {
            fake.charCode = event.keyCode;
            if (!event.shiftKey) {
               fake.charCode |= 0x20; /* if it's not shifted, lower-case it */
            }
            fake.keyCode = 0;
         }
         else
         {
            fake.charCode = 0;
            fake.keyCode = event.keyCode;
            if (!alphNumKey && (event.ctrlKey ||
                                event.altKey ||
                                event.metaKey))
            {
               fake = this.fixEvent(fake);
            }
         }
         if (!asciiKey || fake.charCode != this.lastNormalKeyDownEvent.keyCode)
         {
            this.lastNormalKeyDownEvent = undefined;
            this.handleKey(fake);
         }
      }
   }

   try
   {
     // For IE
     event.cancelBubble = true;
     event.returnValue = false;
   }
   catch (e) { }

   this.lastKeyDownEvent = undefined;
   this.lastKeyPressedEvent = undefined;

   return false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.animateCursor = function(inactive)
{
   if (!this.cursorInterval)
   {
      this.cursorInterval = setInterval(
         function(vt100) {
            return function()
            {
               vt100.animateCursor();
               // Use this opportunity to check whether the user entered a composed
               // key, or whether somebody pasted text into the textfield.
               vt100.checkComposedKeys();
            };
         }(this), 500);
   }

   if (inactive != undefined || this.cursor.className != 'inactive')
   {
      if (inactive) {
         this.cursor.className = 'inactive';
      } else {
         this.cursor.className = this.cursor.className == 'bright'
                                 ? 'dim' : 'bright';
      }
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.blurCursor = function()
{
   this.animateCursor(true);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.focusCursor = function()
{
   this.animateCursor(false);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.beep = function()
{
   if (this.visualBell) this.flashScreen();

   if (this.audibleBell)
   {
      try
      {
         this.beeper.Play();
      }
      catch (e)
      {
         try
         {
           this.beeper.src = 'beep.wav';
         }
         catch (e) { }
      }
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.bs = function()
{
   if (this.cursorX > 0)
   {
      this.gotoXY(this.cursorX - 1, this.cursorY);
      this.needWrap = false;
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.ht = function(count)
{
   if (count == undefined) count = 1;

   var cx = this.cursorX;
   while (count-- > 0)
   {
      while (cx++ < this.terminalWidth)
      {
         var tabState = this.userTabStop[cx];
         if (tabState == false)
         {
            // Explicitly cleared tab stop
            continue;
         }
         else
         if (tabState)
         {
            // Explicitly set tab stop
            break;
         }
         else
         {
            // Default tab stop at each eighth column
            if (cx % 8 == 0) {
               break;
            }
         }
      }
   }

   if (cx > this.terminalWidth - 1) cx = this.terminalWidth - 1;
   if (cx != this.cursorX) this.gotoXY(cx, this.cursorY);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.rt = function(count)
{
   if (count == undefined) count = 1;

   var cx = this.cursorX;
   while (count-- > 0)
   {
      while (cx-- > 0)
      {
         var tabState = this.userTabStop[cx];
         if (tabState == false)
         {
            // Explicitly cleared tab stop
            continue;
         }
         else
         if (tabState)
         {
            // Explicitly set tab stop
            break;
         }
         else
         {
            // Default tab stop at each eighth column
            if (cx % 8 == 0) {
               break;
            }
         }
      }
   }
   if (cx < 0) {
      cx = 0;
   }
   if (cx != this.cursorX) {
      this.gotoXY(cx, this.cursorY);
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.cr = function()
{
   this.gotoXY(0, this.cursorY);
   this.needWrap = false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.lf = function(count)
{
   if (count == undefined)
      count = 1;
   else
   {
      if (count > this.terminalHeight) count  = this.terminalHeight;
      if (count < 1) count  = 1;
   }

   while (count-- > 0)
   {

      if (this.cursorY == this.bottom - 1) {
         this.scrollRegion(0, this.top + 1,
                           this.terminalWidth, this.bottom - this.top - 1,
                           0, -1, this.color, this.style);
      } else
      if (this.cursorY < this.terminalHeight - 1) {
         this.gotoXY(this.cursorX, this.cursorY + 1);
      }
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.ri = function(count)
{
   if (count == undefined)
      count = 1;
   else
   {
      if (count > this.terminalHeight) count = this.terminalHeight;
      if (count < 1) count = 1;
   }

   while (count-- > 0)
   {
      if (this.cursorY == this.top) {
         this.scrollRegion(0, this.top,
                           this.terminalWidth, this.bottom - this.top - 1,
                           0, 1, this.color, this.style);
      } else
      if (this.cursorY > 0) {
         this.gotoXY(this.cursorX, this.cursorY - 1);
      }
   }
   this.needWrap = false;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.respondID = function()
{
   this.respondString += '\u001B[?6c';
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.respondSecondaryDA = function()
{
   this.respondString += '\u001B[>0;0;0c';
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.updateStyle = function()
{
   this.style = '';

   if (this.attr & 0x0800 /* ATTR_BRIGHT */)
   {
      // bold does not seem to be reliable so create our own with a shadow
      this.style += 'text-shadow:0px 1px;';
   }
   else
   if (this.attr & 0x0400 /* ATTR_DIM */) {
      this.style += 'font-weight:lighter;';
   }

   if (this.attr & 0x1000 /* ATTR_BLINK */)
      this.style += 'animation-name:blink;' +
                    'animation-duration:1s;' +
                    'animation-timing-function:step-end;' +
                    'animation-iteration-count:infinite;';

   if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
      this.style += 'text-decoration:underline;';
   }

   var bg = (this.attr >> 4) & 0xf;
   var fg =  this.attr & 0xf;
   var invert = '';

   if (this.attr & 0x0100 /* ATTR_REVERSE */)
   {
      var tmp = fg;
      fg = bg;
      bg = tmp;
      var invert = ' invert';
   }

   this.color = 'ansi' + fg + ' bgAnsi' + bg + invert;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.saveCursor = function()
{
   this.savedX[this.currentScreen] = this.cursorX;
   this.savedY[this.currentScreen] = this.cursorY;
   this.savedAttr[this.currentScreen] = this.attr;
   this.savedUseGMap = this.useGMap;
   for (var i = 0; i < 4; i++) {
      this.savedGMap[i] = this.GMap[i];
   }
   this.savedValid[this.currentScreen] = true;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.restoreCursor = function()
{
   if (!this.savedValid[this.currentScreen]) {
      return;
   }
   this.attr = this.savedAttr[this.currentScreen];
   this.updateStyle();
   this.useGMap = this.savedUseGMap;
   for (var i = 0; i < 4; i++) {
      this.GMap[i] = this.savedGMap[i];
   }
   this.translate = this.GMap[this.useGMap];
   this.needWrap  = false;
   this.gotoXY(this.savedX[this.currentScreen],
               this.savedY[this.currentScreen]);

// on fast CPUs (? ... well experienced only on X86)
// the OpenVMS prompt appears reverse video ... huh?
// unexplained ... this sleep(mS) seems to correct that (?)

   this.sleep(10);
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.setMode = function(state)
{
   for (var i = 0; i <= this.npar; i++)
   {
      if (this.isQuestionMark)
      {
         switch (this.par[i])
         {
            case 1: this.cursorKeyMode = state; break;
            case 3: resizeTerminal(state ? 132 : 80); break;
            case 5: this.isInverted = state;
                    this.refreshInvertedState();
                    break;
            case 6: this.offsetMode = state; break;
            case 7: this.autoWrapMode = state; break;
            case 1000:
            case  9: this.mouseReporting = state; break;
            case 25: this.cursorNeedsShowing = state;
                     if (state) { this.showCursor(); }
                     else       { this.hideCursor(); } break;
            case 1047:
            case 1049:
            case 47: this.enableAlternateScreen(state); break;
            default: break;
         }
      }
      else
      {
         switch (this.par[i])
         {
            case 3: this.dispCtrl = state; break;
            case 4: this.insertMode = state; break;
            case 20:this.crLfMode = state; break;
            default: break;
         }
      }
   }
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.statusReport = function()
{
   // Ready and operational.
   this.respondString += '\u001B[0n';
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.cursorReport = function()
{
   this.respondString += '\u001B[' +
                         (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
                         ';' +
                         (this.cursorX + 1) +
                         'R';
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.setCursorAttr = function(setAttr, xorAttr)
{
   // Changing of cursor color is not implemented.
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.openPrinterWindow = function()
{
   var rc = true;
   try
   {
      if (!this.printWin || this.printWin.closed) {
         this.printWin = window.open('', 'print-output',
            'width=800,height=600,directories=no,location=no,menubar=yes,' +
            'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
         this.printWin.document.body.innerHTML =
            '\n' +
            '
' + 'Automatically, print page(s) when job is ready' + '
\n' + '
 
' + '
\n';
         var autoprint = this.printWin.document.getElementById('autoprint');
         this.addListener(autoprint, 'click',
                          (function(vt100, autoprint) {
                             return function()
                             {
                                vt100.autoprint = autoprint.checked;
                                vt100.storeUserSettings();
                                return false;
                             };
                          })(this, autoprint));
         this.printWin.document.title = 'DCLinabox Printer Output';
      }
   }
   catch (e)
   {
      // Maybe, a popup blocker prevented us from working. Better catch the
      // exception, so that we won't break the entire terminal session. The
      // user probably needs to disable the blocker first before retrying the
      // operation.
      rc = false;
   }
   rc &= this.printWin && !this.printWin.closed &&
         (this.printWin.innerWidth ||
          this.printWin.document.documentElement.clientWidth ||
          this.printWin.document.body.clientWidth) > 1;

   if (!rc && this.printing == 100)
   {
      // Different popup blockers work differently. We try to detect a couple
      // of common methods. And then we retry again a brief amount later, as
      // false positives are otherwise possible. If we are sure that there is
      // a popup blocker in effect, we alert the user to it. This is helpful
      // as some popup blockers have minimal or no UI, and the user might not
      // notice that they are missing the popup. In any case, we only show at
      // most one message per print job.
      this.printing   = true;
      setTimeout((function(win) {
                 return function()
                 {
                    if (!win || win.closed ||
                        (win.innerWidth ||
                         win.document.documentElement.clientWidth ||
                         win.document.body.clientWidth) <= 1) {
                       alert('Attempted to print, but a popup blocker ' +
                             'prevented the printer window from opening');
                    }
                 };
              })(this.printWin), 2000);
   }

   return rc;
};

///////////////////////////////////////////////////////////////////////////////

VT100.prototype.sendToPrinter = function(s)
{
   this.openPrinterWindow();
   try
   {
      var doc = this.printWin.document;
      var print = doc.getElementById('print');
      if (print.lastChild && print.lastChild.nodeName == '#text') {
         print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
      } else {
         print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
      }
   }
   catch (e)
   {
      // There probably was a more aggressive popup blocker that prevented us
      // from accessing the printer windows.
   }
};

///////////////////////////////////////////////////////////////////////////////

// We get called whenever doControl() is active. But for the printer, we
// only implement a basic line printer that doesn't understand most of
// the escape sequences of the VT100 terminal. In fact, the only escape
// sequence that we really need to recognize is '^[[5i' for turning the
// printer off.

VT100.prototype.sendControlToPrinter = function(ch)
{
   try
   {
      switch (ch)
      {
         case  9: /* HT */
            this.openPrinterWindow();
            var doc   = this.printWin.document;
            var print = doc.getElementById('print');
            var chars = print.lastChild &&
                        print.lastChild.nodeName == '#text' ?
                        print.lastChild.textContent.length : 0;
            this.sendToPrinter(this.spaces(8 - (chars % 8)));
            break;

         case 10: /* CR */
            break;

         case 12: /* FF */
            this.openPrinterWindow();
            var pageBreak = this.printWin.document.createElement('div');
            pageBreak.className = 'pagebreak';
            pageBreak.innerHTML = '
'; this.printWin.document.getElementById('print').appendChild(pageBreak); break; case 13: /* LF */ this.openPrinterWindow(); var lineBreak = this.printWin.document.createElement('br'); this.printWin.document.getElementById('print').appendChild(lineBreak); break; case 27: /* ESC */ this.isEsc = 1 /* ESesc */; break; default: { switch (this.isEsc) { case 1 /* ESesc */ : { this.isEsc = 0 /* ESnormal */; switch (ch) { case 0x5B /*[*/ : this.isEsc = 2 /* ESsquare */; break; default: break; } break; } case 2 /* ESsquare */ : { this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; this.isEsc = 3 /* ESgetpars */; this.isQuestionMark = (ch == 0x3F) /*?*/; if (this.isQuestionMark) { break; } // Fall through } case 3 /* ESgetpars */ : { if (ch == 0x3B /*;*/) { this.npar++; break; } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) { var par = this.par[this.npar]; if (par == undefined) { par = 0; } this.par[this.npar] = 10*par + (ch & 0xF); break; } else { this.isEsc = 4 /* ESgotpars */; } // Fall through } case 4 /* ESgotpars */ : { this.isEsc = 0 /* ESnormal */; if (this.isQuestionMark) { break; } switch (ch) { case 0x69 /*i*/ : this.csii(this.par[0]); break; default: break; } break; } default: this.isEsc = 0 /* ESnormal */; break; } break; } } } catch (e) { // There probably was a more aggressive popup blocker that prevented // us from accessing the printer windows. } }; /////////////////////////////////////////////////////////////////////////////// // Insert spaces VT100.prototype.csiAt = function(number) { if (number == 0) number = 1; if (number > this.terminalWidth - this.cursorX) number = this.terminalWidth - this.cursorX; this.scrollRegion(this.cursorX, this.cursorY, this.terminalWidth - this.cursorX - number, 1, number, 0, this.color, this.style); this.needWrap = false; }; /////////////////////////////////////////////////////////////////////////////// // Printer control VT100.prototype.csii = function(number) { switch (number) { case 0: // Print Screen window.print(); break; case 4: // Stop printing try { if (this.printing && this.printWin && !this.printWin.closed) { var print = this.printWin.document.getElementById('print'); while (print.lastChild && print.lastChild.tagName == 'DIV' && print.lastChild.className == 'pagebreak') { // Remove trailing blank pages print.removeChild(print.lastChild); } if (this.autoprint) { this.printWin.print(); } } } catch (e) { } this.printing = false; break; case 5: // Start printing if (!this.printing && this.printWin && !this.printWin.closed) { this.printWin.document.getElementById('print').innerHTML = ''; } this.printing = 100; break; default: break; } }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.csiJ = function(number) { switch (number) { case 0: // Erase from cursor to end of display this.clearRegion(this.cursorX, this.cursorY, this.terminalWidth - this.cursorX, 1, this.color, this.style); if (this.cursorY < this.terminalHeight-2) { this.clearRegion(0, this.cursorY+1, this.terminalWidth, this.terminalHeight-this.cursorY-1, this.color, this.style); } break; case 1: // Erase from start to cursor if (this.cursorY > 0) { this.clearRegion(0, 0, this.terminalWidth, this.cursorY, this.color, this.style); } this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.color, this.style); break; case 2: // Erase whole display this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.color, this.style); break; default: return; } this.needwrap = false; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.csiK = function(number) { switch (number) { case 0: // Erase from cursor to end of line this.clearRegion(this.cursorX, this.cursorY, this.terminalWidth - this.cursorX, 1, this.color, this.style); break; case 1: // Erase from start of line to cursor this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.color, this.style); break; case 2: // Erase whole line this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.color, this.style); break; default: return; } this.needwrap = false; }; /////////////////////////////////////////////////////////////////////////////// // Open line by inserting blank line(s) VT100.prototype.csiL = function(number) { if (this.cursorY >= this.bottom) return; if (number == 0) number = 1; if (number > this.bottom - this.cursorY) number = this.bottom - this.cursorY; this.scrollRegion(0, this.cursorY, this.terminalWidth, this.bottom - this.cursorY - number, 0, number, this.color, this.style); this.needwrap = false; }; /////////////////////////////////////////////////////////////////////////////// // Delete line(s), scrolling up the bottom of the screen. VT100.prototype.csiM = function(number) { if (this.cursorY >= this.bottom) return; if (number == 0) number = 1; if (number > this.bottom - this.cursorY) number = this.bottom - this.cursorY; this.scrollRegion(0, this.cursorY + number, this.terminalWidth, this.bottom - this.cursorY - number, 0, -number, this.color, this.style); this.needwrap = false; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.csim = function() { for (var i = 0; i <= this.npar; i++) { switch (this.par[i]) { case 0 : this.attr &= 0x00ff; break; case 1 : this.attr = (this.attr & ~0x0400 /* ATTR_DIM */) | 0x0800 /* ATTR_BRIGHT */; break; case 2 : this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */) | 0x0400 /* ATTR_DIM */; break; case 4 : this.attr |= 0x0200 /* ATTR_UNDERLINE */; break; case 5 : this.attr |= 0x1000 /* ATTR_BLINK */; break; case 7 : this.attr |= 0x0100 /* ATTR_REVERSE */; break; case 10 : this.translate = this.GMap[this.useGMap]; this.dispCtrl = false; this.toggleMeta = false; break; case 11: this.translate = this.CodePage437Map; this.dispCtrl = true; this.toggleMeta = false; break; case 12: this.translate = this.CodePage437Map; this.dispCtrl = true; this.toggleMeta = true; break; case 21: case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */ | 0x0400 /* ATTR_DIM */); break; case 24: this.attr &= ~0x0200 /* ATTR_UNDERLINE */; break; case 25: this.attr &= ~0x1000 /* ATTR_BLINK */; break; case 27: this.attr &= ~0x0100 /* ATTR_REVERSE */; break; case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */ | 0x0800 /* ATTR_BRIGHT */|0x0F)) | 0x0200 /* ATTR_UNDERLINE */; break; case 39: this.attr &= ~(0x0400 /* ATTR_DIM */ | 0x0800 /* ATTR_BRIGHT */ | 0x0200 /* ATTR_UNDERLINE */|0x0F); break; case 49: this.attr |= 0xF0; break; default: { if (this.par[i] >= 30 && this.par[i] <= 37) { var fg = this.par[i] - 30; this.attr = (this.attr & ~0x0F) | fg; } else if (this.par[i] >= 40 && this.par[i] <= 47) { var bg = this.par[i] - 40; this.attr = (this.attr & ~0xF0) | (bg << 4); } break; } } } this.updateStyle(); }; /////////////////////////////////////////////////////////////////////////////// // Delete character(s) following cursor VT100.prototype.csiP = function(number) { if (number == 0) number = 1; if (number > this.terminalWidth - this.cursorX) number = this.terminalWidth - this.cursorX; this.scrollRegion(this.cursorX + number, this.cursorY, this.terminalWidth - this.cursorX - number, 1, -number, 0, this.color, this.style); this.needwrap = false; }; /////////////////////////////////////////////////////////////////////////////// // Clear characters following cursor VT100.prototype.csiX = function(number) { if (number == 0) number = 1; if (number > this.terminalWidth - this.cursorX) number = this.terminalWidth - this.cursorX; this.clearRegion(this.cursorX, this.cursorY, number, 1, this.color, this.style); this.needwrap = false; }; /////////////////////////////////////////////////////////////////////////////// // double-height, double-width characters // // Unlike most other styling in VT100.JS being applied to intra-line s // containing text these are applied (and propagated) to the per-line
s // and so are applied to every on the associated line. VT100.prototype.csiHash = function(ch) { if (ch == 0x38) { /* DECALN (screen alignment test - and used by vttest) */ var ees = ''; for (var x = 0; x < this.terminalWidth; x++) ees += 'E'; for (var y = 0; y < this.terminalHeight; y++) this.putString(0, y, ees, undefined, undefined); this.gotoXY(0, 0); return; } var line = this.findLine (this.cursorY); switch (ch) { case 0x33 /* DECDHL top */ : line.DECstyle = 'transform:translate(50%,50%)scale(2.0,2.0);'; break; case 0x34 /* DECDHL bottom */ : line.DECstyle = 'visibility:hidden;'; break; case 0x35 /* DECSWL */ : line.DECstyle = undefined; break; case 0x36 /* DECDWL */ : line.DECstyle = 'transform:translateX(50%)scaleX(2.0);'; break; default : return; } line.style.cssText = line.DECstyle; }; /////////////////////////////////////////////////////////////////////////////// // control keyboard LEDs VT100.prototype. csiq = function(par) { if (!DCLinaboxLEDs) return; if (typeof this.leds == 'undefined') { this.leds = { ledon : 'red', ledoff : 'transparent', led1 : document.getElementById('led1'), led2 : document.getElementById('led2'), led3 : document.getElementById('led3'), led4 : document.getElementById('led4') }; } var leds = this.leds; for (var i = 0; i <= this.npar; i++) { switch (this.par[i]) { case 0 : leds.led1.style.backgroundColor = leds.led2.style.backgroundColor = leds.led3.style.backgroundColor = leds.led4.style.backgroundColor = leds.ledoff; break; case 1 : leds.led1.style.backgroundColor = leds.ledon; break; case 2 : leds.led2.style.backgroundColor = leds.ledon; break; case 3 : leds.led3.style.backgroundColor = leds.ledon; break; case 4 : leds.led4.style.backgroundColor = leds.ledon; break; } } }; /////////////////////////////////////////////////////////////////////////////// // Enable Locator Reporting (DECELR). // CSI Ps ; Pu ' z // Valid values for the first parameter: // Ps = 0 -> Locator disabled (default). // Ps = 1 -> Locator enabled. // Ps = 2 -> Locator enabled for one report, then disabled. // The second parameter specifies the coordinate unit for locator ports. // Valid values for the second parameter: // Pu = 0 <- or omitted -> default to character cells. // Pu = 1 <- device physical pixels. // Pu = 2 <- character cells. VT100.prototype.csiDECELR = function(par) { this.mouseReporting = true; switch (par[0]) { case 1 : this.DECelr = 1; break; case 2 : this.DECelr = 2; break; default : this.mouseReporting = false; this.DECelr = 0; } switch (par[1]) { /* if pixels are selected then just cancel the whole ELR */ case 1 : this.mouseReporting = false; this.DECelr = 0; break; } }; /////////////////////////////////////////////////////////////////////////////// // Select Locator Events (DECSLE). // CSI Ps ' { // Valid values for the first (and any additional parameters) are: // Ps = 0 -> only respond to explicit host requests (DECRQLP). // (This is default). It also cancels any filter rectangle. // Ps = 1 -> report button down transitions. // Ps = 2 -> do not report button down transitions. // Ps = 3 -> report button up transitions. // Ps = 4 -> do not report button up transitions. VT100.prototype.csiDECSLE = function(par) { switch (par[0]) { case 1 : this.DECsle = 1; break; case 2 : this.DECsle = 2; break; case 3 : this.DECsle = 3; break; case 4 : this.DECsle = 4; break; default : this.DECsle = 0; } }; /////////////////////////////////////////////////////////////////////////////// // Request Locator Position (DECRQLP). // CSI Ps ' | // Valid values for the parameter are: // Ps = 0 , 1 or omitted -> transmit a single DECLRP locator report. // // If Locator Reporting has been enabled by a DECELR, xterm will // respond with a DECLRP Locator Report. This report is also // generated on button up and down events if they have been // enabled with a DECSLE, or when the locator is detected outside // of a filter rectangle, if filter rectangles have been enabled // with a DECEFR. // // -> CSI Pe ; Pb ; Pr ; Pc ; Pp & w // // Parameters are [event;button;row;column;page]. // Valid values for the event: // Pe = 0 -> locator unavailable - no other parameters sent. // Pe = 1 -> request - xterm received a DECRQLP. // Pe = 2 -> left button down. // Pe = 3 -> left button up. // Pe = 4 -> middle button down. // Pe = 5 -> middle button up. // Pe = 6 -> right button down. // Pe = 7 -> right button up. // Pe = 8 -> M4 button down. // Pe = 9 -> M4 button up. // Pe = 1 0 -> locator outside filter rectangle. // The ``button'' parameter is a bitmask indicating which buttons // are pressed: // Pb = 0 <- no buttons down. // Pb & 1 <- right button down. // Pb & 2 <- middle button down. // Pb & 4 <- left button down. // Pb & 8 <- M4 button down. // The ``row'' and ``column'' parameters are the coordinates of // the locator position in the xterm window, encoded as ASCII decimal. // The ``page'' parameter is not used by xterm. VT100.prototype.csiDECRQLP = function(par) { this.mouseReporting = true; this.DECelr = 2; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.settermCommand = function() { // Setterm commands are not implemented }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.doControl = function(ch) { if (this.printing) { this.sendControlToPrinter(ch); return ''; } var lineBuf = ''; switch (ch) { case 0x00: /* ignored */ break; case 0x08: this.bs(); break; case 0x09: this.ht(); break; case 0x0A: case 0x0B: case 0x0C: case 0x84: this.lf(); if (!this.crLfMode) break; case 0x0D: this.cr(); break; case 0x85: this.cr(); this.lf(); break; case 0x0E: this.useGMap = 1; this.translate = this.GMap[1]; this.dispCtrl = true; break; case 0x0F: this.useGMap = 0; this.translate = this.GMap[0]; this.dispCtrl = false; break; case 0x18: case 0x1A: this.isEsc = 0 /* ESnormal */; break; case 0x1B: this.isEsc = 1 /* ESesc */; break; case 0x7F: /* ignored */ break; case 0x88: this.userTabStop[this.cursorX] = true; break; case 0x8D: this.ri(); break; case 0x8E: this.isEsc = 18 /* ESss2 */; break; case 0x8F: this.isEsc = 19 /* ESss3 */; break; case 0x9A: this.respondID(); break; case 0x9B: this.isEsc = 2 /* ESsquare */; break; case 0x07: if (this.isEsc != 17 /* ESstatus */) { this.beep(); break; } /* fall thru */ default: { switch (this.isEsc) { case 1 /* ESesc */ : { this.isEsc = 0 /* ESnormal */; switch (ch) { case 0x25 /*%*/ : this.isEsc = 13 /* ESpercent */; break; case 0x28 /*(*/ : this.isEsc = 8 /* ESsetG0 */; break; case 0x2D /*-*/ : case 0x29 /*)*/ : this.isEsc = 9 /* ESsetG1 */; break; case 0x2E /*.*/ : case 0x2A /***/ : this.isEsc = 10 /* ESsetG2 */; break; case 0x2F /*/*/ : case 0x2B /*+*/ : this.isEsc = 11 /* ESsetG3 */; break; case 0x23 /*#*/ : this.isEsc = 7 /* EShash */; break; case 0x37 /*7*/ : this.saveCursor(); break; case 0x38 /*8*/ : this.restoreCursor(); break; case 0x3E /*>*/ : this.applKeyMode = false; break; case 0x3D /*=*/ : this.applKeyMode = true; break; case 0x44 /*D*/ : this.lf(); break; case 0x45 /*E*/ : this.cr(); this.lf(); break; case 0x4D /*M*/ : this.ri(); break; case 0x4E /*N*/ : this.isEsc = 18 /* ESss2 */; break; case 0x4F /*O*/ : this.isEsc = 19 /* ESss3 */; break; case 0x48 /*H*/ : this.userTabStop[this.cursorX] = true; break; case 0x5A /*Z*/ : this.respondID(); break; case 0x5B /*[*/ : this.isEsc = 2 /* ESsquare */; break; case 0x5D /*]*/ : this.isEsc = 15 /* ESnonstd */; break; case 0x63 /*c*/ : this.reset(); break; case 0x67 /*g*/ : this.beep(); break; default: break; } break; } case 15 /* ESnonstd */ : { switch (ch) { case 0x00 /*0*/ : case 0x31 /*1*/ : case 0x32 /*2*/ : this.statusString = ''; this.isEsc = 17 /* ESstatus */; break; case 0x50 /*P*/ : this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ]; this.isEsc = 16 /* ESpalette */; break; case 0x52 /*R*/ : // Palette support is not implemented this.isEsc = 0 /* ESnormal */; break; default: this.isEsc = 0 /* ESnormal */; break; } break; } case 16 /* ESpalette */ : { if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) || (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) || (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) { this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55 : (ch & 0xF); if (this.npar == 7) { // Palette support is not implemented this.isEsc = 0 /* ESnormal */; } } else { this.isEsc = 0 /* ESnormal */; } break; } case 2 /* ESsquare */ : { this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; this.isEsc = 3 /* ESgetpars */; if (ch == 0x5B /*[*/) { // Function key this.isEsc = 6 /* ESfunckey */; break; } else { this.isQuestionMark = (ch == 0x3F /*?*/); if (this.isQuestionMark) { break; } } // Fall through } case 5 /* ESdeviceattr */ : case 3 /* ESgetpars */ : { if (ch == 0x3B /*;*/) { this.npar++; break; } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) { var par = this.par[this.npar]; if (par == undefined) { par = 0; } this.par[this.npar] = 10*par + (ch & 0xF); break; } else if (this.isEsc == 5 /* ESdeviceattr */) { switch (ch) { case 0x63: /*c*/ if (this.par[0] == 0) this.respondSecondaryDA(); break; case 0x6D: /*m*/ /* (re)set key modifier resource values */ break; case 0x6E: /*n*/ /* disable key modifier resource values */ break; case 0x70: /*p*/ /* set pointer mode resource value */ break; default: break; } this.isEsc = 0 /* ESnormal */; break; } else { this.isEsc = 4 /* ESgotpars */; } // Fall through } case 4 /* ESgotpars */ : { this.isEsc = 0; /* ESnormal */ if (this.isQuestionMark) { switch (ch) { case 0x68 /*h*/ : this.setMode(true); break; case 0x6C /*l*/ : this.setMode(false); break; case 0x63 /*c*/ : this.setCursorAttr(this.par[2], this.par[1]); break; default: break; } this.isQuestionMark = false; break; } switch (ch) { case 0x21 /*!*/ : this.isEsc = 12 /* ESbang */; break; case 0x27 /*'*/ : this.isEsc = 20 /* ESsquote */; break; case 0x3E /*>*/ : if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break; case 0x47 /*G*/ : case 0x60 /*`*/ : this.gotoXY(this.par[0] - 1, this.cursorY); break; case 0x41/*A*/ : this.gotoXY(this.cursorX, this.cursorY - (this.par[0] ? this.par[0] : 1)); break; case 0x42 /*B*/ : case 0x65 /*e*/ : this.gotoXY(this.cursorX, this.cursorY + (this.par[0] ? this.par[0] : 1)); break; case 0x43 /*C*/ : case 0x61 /*a*/ : this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1), this.cursorY); break; case 0x44 /*D*/ : this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1), this.cursorY); break; case 0x45 /*E*/ : this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1)); break; case 0x46 /*F*/ : this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1)); break; case 0x64 /*d*/ : this.gotoXaY(this.cursorX, this.par[0] - 1); break; case 0x48 /*H*/ : case 0x66 /*f*/ : this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break; case 0x49 /*I*/ : this.ht(this.par[0] ? this.par[0] : 1); break; case 0x40 /*@*/ : this.csiAt(this.par[0]); break; case 0x69 /*i*/ : this.csii(this.par[0]); break; case 0x4A /*J*/ : this.csiJ(this.par[0]); break; case 0x4B /*K*/ : this.csiK(this.par[0]); break; case 0x4C /*L*/ : this.csiL(this.par[0]); break; case 0x4D /*M*/ : this.csiM(this.par[0]); break; case 0x6D /*m*/ : this.csim(); break; case 0x50 /*P*/ : this.csiP(this.par[0]); break; case 0x58 /*X*/ : this.csiX(this.par[0]); break; case 0x53 /*S*/ : this.lf(this.par[0] ? this.par[0] : 1); break; case 0x54 /*T*/ : this.ri(this.par[0] ? this.par[0] : 1); break; case 0x63 /*c*/ : if (!this.par[0]) this.respondID(); break; case 0x67 /*g*/ : if (this.par[0] == 0) { this.userTabStop[this.cursorX] = false; } else if (this.par[0] == 3) { this.userTabStop = [ ]; for (var i = 0; i < this.terminalWidth; i++) { this.userTabStop[i] = false; } } break; case 0x68 /*h*/ : this.setMode(true); break; case 0x6C /*l*/ : this.setMode(false); break; case 0x6E /*n*/ : switch (this.par[0]) { case 5: this.statusReport(); break; case 6: this.cursorReport(); break; default: break; } break; case 0x71 /*q*/ : this.csiq(); break; case 0x72 /*r*/ : var t = this.par[0] ? this.par[0] : 1; var b = this.par[1] ? this.par[1] : this.terminalHeight; if (t < b && b <= this.terminalHeight) { this.top = t - 1; this.bottom= b; this.gotoXaY(0, 0); } break; case 0x62 /*b*/ : var c = this.par[0] ? this.par[0] : 1; if (c > this.terminalWidth * this.terminalHeight) { c = this.terminalWidth * this.terminalHeight; } while (c-- > 0) { lineBuf += this.lastCharacter; } break; case 0x73 /*s*/ : this.saveCursor(); break; case 0x75 /*u*/ : this.restoreCursor(); break; case 0x5A /*Z*/ : this.rt(this.par[0] ? this.par[0] : 1); break; case 0x5D /*]*/ : this.settermCommand(); break; default: break; } break; } case 12 /* ESbang */ : { if (ch == 'p') { this.reset(); } this.isEsc = 0 /* ESnormal */; break; } case 13 /* ESpercent */ : { this.isEsc = 0 /* ESnormal */; switch (ch) { case 0x40 /*@*/ : this.utfEnabled = false; break; case 0x47 /*G*/ : case 0x38 /*8*/ : this.utfEnabled = true; break; default: break; } break; } case 6 /* ESfunckey */ : { this.isEsc = 0 /* ESnormal */; break; } case 7 /* EShash */ : { this.isEsc = 0 /* ESnormal */; this.csiHash(ch); break; } case 8 /* ESsetG0 */ : case 9 /* ESsetG1 */ : case 10 /* ESsetG2 */ : case 11 /* ESsetG3 */ : { var g = this.isEsc - 8 /* ESsetG0 */; this.isEsc = 0 /* ESnormal */; switch (ch) { case 0x30 /*0*/ : this.GMap[g] = this.VT100GraphicsMap; break; case 0x42 /*A*/ : case 0x42 /*B*/ : this.GMap[g] = this.Latin1Map; break; case 0x55 /*U*/ : this.GMap[g] = this.CodePage437Map; break; case 0x4B /*K*/ : this.GMap[g] = this.DirectToFontMap; break; default: break; } if (this.useGMap == g) { this.translate = this.GMap[g]; } break; } case 17 /* ESstatus */ : { if (ch == 0x07) { if (this.statusString && this.statusString.charAt(0) == ';') { this.statusString = this.statusString.substr(1); } try { window.status = this.statusString; } catch (e) { } this.isEsc = 0 /* ESnormal */; } else { this.statusString += String.fromCharCode(ch); } break; } case 18 /* ESss2 */ : case 19 /* ESss3 */ : { if (ch < 256) { ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2] [this.toggleMeta ? (ch | 0x80) : ch]; if ((ch & 0xFF00) == 0xF000) { ch = ch & 0xFF; } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) { this.isEsc = 0 /* ESnormal */; break; } } this.lastCharacter = String.fromCharCode(ch); lineBuf += this.lastCharacter; this.isEsc = 0 /* ESnormal */; break; } case 20 /* ESsquote */ : { switch (ch) { case 0x7a /*z*/ : this.csiDECELR(this.par); break; case 0x7b /*{*/ : this.csiDECSLE(this.par); break; case 0x7c /*|*/ : this.csiDECRQLP(this.par); break; default: break; } this.isEsc = 0 /* ESnormal */; break; } default: this.isEsc = 0 /* ESnormal */; break; } break; } } return lineBuf; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.renderString = function(s, showCursor) { if (this.printing) { this.sendToPrinter(s); if (showCursor) { this.showCursor(); } return; } // We try to minimize the number of DOM operations by coalescing individual // characters into strings. This is a significant performance improvement. var incX = s.length; if (incX > this.terminalWidth - this.cursorX) { incX = this.terminalWidth - this.cursorX; if (incX <= 0) { return; } s = s.substr(0, incX - 1) + s.charAt(s.length - 1); } if (showCursor) { // Minimize the number of calls to putString(), by avoiding a direct // call to this.showCursor() this.cursor.style.visibility = ''; } this.putString(this.cursorX, this.cursorY, s, this.color, this.style); }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.vt100 = function(s) { this.cursorNeedsShowing = this.hideCursor(); this.respondString = ''; var lineBuf = ''; for (var i = 0; i < s.length; i++) { var ch = s.charCodeAt(i); if (this.utfEnabled) { // Decode UTF8 encoded character if (ch > 0x7F) { if (this.utfCount > 0 && (ch & 0xC0) == 0x80) { this.utfChar = (this.utfChar << 6) | (ch & 0x3F); if (--this.utfCount <= 0) { if (this.utfChar > 0xFFFF || this.utfChar < 0) { ch = 0xFFFD; } else { ch = this.utfChar; } } else { continue; } } else { if ((ch & 0xE0) == 0xC0) { this.utfCount = 1; this.utfChar = ch & 0x1F; } else if ((ch & 0xF0) == 0xE0) { this.utfCount = 2; this.utfChar = ch & 0x0F; } else if ((ch & 0xF8) == 0xF0) { this.utfCount = 3; this.utfChar = ch & 0x07; } else if ((ch & 0xFC) == 0xF8) { this.utfCount = 4; this.utfChar = ch & 0x03; } else if ((ch & 0xFE) == 0xFC) { this.utfCount = 5; this.utfChar = ch & 0x01; } else { this.utfCount = 0; } continue; } } else { this.utfCount = 0; } } var isNormalCharacter = (ch >= 32 && ch <= 127 || ch >= 160 || this.utfEnabled && ch >= 128 || !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) && (ch != 0x7F || this.dispCtrl); if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) { if (ch < 256) { ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch]; } if ((ch & 0xFF00) == 0xF000) { ch = ch & 0xFF; } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) { continue; } if (!this.printing) { if (this.needWrap || this.insertMode) { if (lineBuf) { this.renderString(lineBuf); lineBuf = ''; } } if (this.needWrap) { this.cr(); this.lf(); } if (this.insertMode) { this.scrollRegion(this.cursorX, this.cursorY, this.terminalWidth - this.cursorX - 1, 1, 1, 0, this.color, this.style); } } this.lastCharacter = String.fromCharCode(ch); lineBuf += this.lastCharacter; if (!this.printing && this.cursorX + lineBuf.length >= this.terminalWidth) { this.needWrap = this.autoWrapMode; } } else { if (lineBuf) { this.renderString(lineBuf); lineBuf = ''; } var expand = this.doControl(ch); if (expand.length) { var r = this.respondString; this.respondString= r + this.vt100(expand); } } } if (lineBuf) { this.renderString(lineBuf, this.cursorNeedsShowing); } else if (this.cursorNeedsShowing) { this.showCursor(); } return this.respondString; }; /////////////////////////////////////////////////////////////////////////////// VT100.prototype.sleep = function(mSecs) { var start = new Date().getTime(); for (var i = 0; i < 1e7; i++) { if ((new Date().getTime() - start) > mSecs) { break; } } } /////////////////////////////////////////////////////////////////////////////// VT100.prototype.Latin1Map = [ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF ]; // MGD 17-DEC-2011 fix zero character, 0x2588 changed to 0x0030 // MGD 17-APR-2021 0x2591 abetter width than 0x2592 VT100.prototype.VT100GraphicsMap = [ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0, 0x25C6, 0x2591, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1, 0x240A, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF ]; VT100.prototype.CodePage437Map = [ 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302, 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 ]; VT100.prototype.DirectToFontMap = [ 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007, 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F, 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017, 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F, 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027, 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F, 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037, 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F, 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047, 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F, 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057, 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F, 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067, 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F, 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077, 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F, 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087, 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F, 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097, 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F, 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7, 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF, 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7, 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF, 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7, 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF, 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7, 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF, 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7, 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF, 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7, 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF ]; VT100.prototype.ctrlAction = [ true, false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, true, false, true, true, false, false, false, false ]; VT100.prototype.ctrlAlways = [ true, false, false, false, false, false, false, false, true, false, true, false, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false ]; ///////////////////////////////////////////////////////////////////////////////