// VT100.js -- JavaScript based terminal emulator // Copyright (C) 2008-2010 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. #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 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) { if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) { this.urlRE = null; } else { this.urlRE = new RegExp( // Known URL protocol are "http", "https", and "ftp". '(?:http|https|ftp)://' + // Optionally allow username and passwords. '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' + // Hostname. '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' + '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' + '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' + // Port '(?::[1-9][0-9]*)?' + // Path. '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' + (linkifyURLs <= 1 ? '' : // Also support URLs without a protocol (assume "http"). // Optional username and password. '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' + // Hostnames must end with a well-known top-level domain or must be // numeric. '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' + 'localhost|' + '(?:(?!-)' + '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' + '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+ 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' + 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' + 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' + 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' + 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' + 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' + 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' + 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' + 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' + 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' + 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' + 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' + // Port '(?::[1-9][0-9]{0,4})?' + // Path. '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') + // In addition, support e-mail address. Optionally, recognize "mailto:" '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') + // Username: '[-_.+a-zA-Z0-9]+@' + // Hostname. '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' + '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+ 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' + 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' + 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' + 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' + 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' + 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' + 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' + 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' + 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' + 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' + 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' + 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' + // Optional arguments '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?'); } this.getUserSettings(); this.initializeElements(container); this.maxScrollbackLines = 500; 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 = 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.mouseReporting = false; this.printing = false; 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.color = 'ansi0 bgAnsi15'; this.style = ''; this.attr = ATTR_DEFAULT; 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 = [ ]; if (clearHistory) { for (var i = 0; i < 2; i++) { while (this.console[i].firstChild) { this.console[i].removeChild(this.console[i].firstChild); } } } this.enableAlternateScreen(false); var wasCompressed = false; var transform = this.getTransformName(); if (transform) { for (var i = 0; i < 2; ++i) { wasCompressed |= this.console[i].style[transform] != ''; this.console[i].style[transform] = ''; } this.cursor.style[transform] = ''; this.space.style[transform] = ''; if (transform == 'filter') { this.console[this.currentScreen].style.width = ''; } } this.scale = 1.0; if (wasCompressed) { this.resizer(); } this.gotoXY(0, 0); this.showCursor(); this.isInverted = false; this.refreshInvertedState(); this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.color, this.style); }; VT100.prototype.addListener = function(elem, event, listener) { try { if (elem.addEventListener) { elem.addEventListener(event, listener, false); } else { elem.attachEvent('on' + event, listener); } } catch (e) { } }; 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 = 3; this.utfPreferred = true; this.visualBell = typeof suppressAllAudio != 'undefined' && suppressAllAudio; this.autoprint = true; this.softKeyboard = false; this.blinkingCursor = true; if (this.visualBell) { this.signature = Math.floor(16807*this.signature + 1) % ((1 << 31) - 1); } if (typeof userCSSList != 'undefined') { for (var i = 0; i < userCSSList.length; ++i) { var label = userCSSList[i][0]; for (var j = 0; j < label.length; ++j) { this.signature = Math.floor(16807*this.signature+ label.charCodeAt(j)) % ((1 << 31) - 1); } if (userCSSList[i][1]) { this.signature = Math.floor(16807*this.signature + 1) % ((1 << 31) - 1); } } } var key = 'shellInABox=' + 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 == 5 + (typeof userCSSList == 'undefined' ? 0 : userCSSList.length)) { this.utfPreferred = settings.charAt(0) != '0'; this.visualBell = settings.charAt(1) != '0'; this.autoprint = settings.charAt(2) != '0'; this.softKeyboard = settings.charAt(3) != '0'; this.blinkingCursor = settings.charAt(4) != '0'; if (typeof userCSSList != 'undefined') { for (var i = 0; i < userCSSList.length; ++i) { userCSSList[i][2] = settings.charAt(i + 5) != '0'; } } } } this.utfEnabled = this.utfPreferred; }; VT100.prototype.storeUserSettings = function() { var settings = 'shellInABox=' + this.signature + ':' + (this.utfEnabled ? '1' : '0') + (this.visualBell ? '1' : '0') + (this.autoprint ? '1' : '0') + (this.softKeyboard ? '1' : '0') + (this.blinkingCursor ? '1' : '0'); if (typeof userCSSList != 'undefined') { for (var i = 0; i < userCSSList.length; ++i) { settings += userCSSList[i][2] ? '1' : '0'; } } var d = new Date(); d.setDate(d.getDate() + 3653); document.cookie = settings + ';expires=' + d.toGMTString(); }; VT100.prototype.initializeUserCSSStyles = function() { this.usercssActions = []; if (typeof userCSSList != 'undefined') { var menu = ''; var group = ''; var wasSingleSel = 1; var beginOfGroup = 0; for (var i = 0; i <= userCSSList.length; ++i) { if (i < userCSSList.length) { var label = userCSSList[i][0]; var newGroup = userCSSList[i][1]; var enabled = userCSSList[i][2]; // Add user style sheet to document var style = document.createElement('link'); var id = document.createAttribute('id'); id.nodeValue = 'usercss-' + i; style.setAttributeNode(id); var rel = document.createAttribute('rel'); rel.nodeValue = 'stylesheet'; style.setAttributeNode(rel); var href = document.createAttribute('href'); href.nodeValue = 'usercss-' + i + '.css'; style.setAttributeNode(href); var type = document.createAttribute('type'); type.nodeValue = 'text/css'; style.setAttributeNode(type); document.getElementsByTagName('head')[0].appendChild(style); style.disabled = !enabled; } // Add entry to menu if (newGroup || i == userCSSList.length) { if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) { // The last group had multiple entries that are mutually exclusive; // or the previous to last group did. In either case, we need to // append a "
" before we can add the last group to the menu. menu += '
'; } wasSingleSel = i - beginOfGroup < 1; menu += group; group = ''; for (var j = beginOfGroup; j < i; ++j) { this.usercssActions[this.usercssActions.length] = function(vt100, current, begin, count) { // Deselect all other entries in the group, then either select // (for multiple entries in group) or toggle (for on/off entry) // the current entry. return function() { var entry = vt100.getChildById(vt100.menu, 'beginusercss'); var i = -1; var j = -1; for (var c = count; c > 0; ++j) { if (entry.tagName == 'LI') { if (++i >= begin) { --c; var label = vt100.usercss.childNodes[j]; // Restore label to just the text content if (typeof label.textContent == 'undefined') { var s = label.innerText; label.innerHTML = ''; label.appendChild(document.createTextNode(s)); } else { label.textContent= label.textContent; } // User style sheets are numbered sequentially var sheet = document.getElementById( 'usercss-' + i); if (i == current) { if (count == 1) { sheet.disabled = !sheet.disabled; } else { sheet.disabled = false; } if (!sheet.disabled) { label.innerHTML= '' + label.innerHTML; } } else { sheet.disabled = true; } userCSSList[i][2] = !sheet.disabled; } } entry = entry.nextSibling; } // If the font size changed, adjust cursor and line dimensions this.cursor.style.cssText= ''; this.cursorWidth = this.cursor.clientWidth; this.cursorHeight = this.lineheight.clientHeight; for (i = 0; i < this.console.length; ++i) { for (var line = this.console[i].firstChild; line; line = line.nextSibling) { line.style.height = this.cursorHeight + 'px'; } } vt100.resizer(); }; }(this, j, beginOfGroup, i - beginOfGroup); } if (i == userCSSList.length) { break; } beginOfGroup = i; } // Collect all entries in a group, before attaching them to the menu. // This is necessary as we don't know whether this is a group of // mutually exclusive options (which should be separated by "
" on // both ends), or whether this is a on/off toggle, which can be grouped // together with other on/off options. group += '
  • ' + (enabled ? '' : '') + label + '
  • '; } this.usercss.innerHTML = menu; } }; VT100.prototype.resetLastSelectedKey = function(e) { var key = this.lastSelectedKey; if (!key) { return false; } var position = this.mousePosition(e); // We don't get all the necessary events to reliably reselect a key // if we moved away from it and then back onto it. We approximate the // behavior by remembering the key until either we release the mouse // button (we might never get this event if the mouse has since left // the window), or until we move away too far. var box = this.keyboard.firstChild; if (position[0] < box.offsetLeft + key.offsetWidth || position[1] < box.offsetTop + key.offsetHeight || position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth || position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight || position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth || position[1] < box.offsetTop + key.offsetTop - key.offsetHeight || position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth || position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) { if (this.lastSelectedKey.className) log.console('reset: deselecting'); this.lastSelectedKey.className = ''; this.lastSelectedKey = undefined; } return false; }; VT100.prototype.showShiftState = function(state) { var style = document.getElementById('shift_state'); if (state) { this.setTextContentRaw(style, '#vt100 #keyboard .shifted {' + 'display: inline }' + '#vt100 #keyboard .unshifted {' + 'display: none }'); } else { this.setTextContentRaw(style, ''); } var elems = this.keyboard.getElementsByTagName('I'); for (var i = 0; i < elems.length; ++i) { if (elems[i].id == '16') { elems[i].className = state ? 'selected' : ''; } } }; VT100.prototype.showCtrlState = function(state) { var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */); if (ctrl) { ctrl.className = state ? 'selected' : ''; } }; VT100.prototype.showAltState = function(state) { var alt = this.getChildById(this.keyboard, '18' /* Alt */); if (alt) { alt.className = state ? 'selected' : ''; } }; VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){ var fake = [ ]; fake.charCode = ch; fake.keyCode = key; fake.ctrlKey = ctrl; fake.shiftKey = shift; fake.altKey = alt; fake.metaKey = alt; return this.handleKey(fake); }; VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) { if (elem == undefined) { return; } if (ch == '\u00A0') { //   should be treated as a regular space character. ch = ' '; } if (ch != undefined && CH == undefined) { // For letter keys, we automatically compute the uppercase character code // from the lowercase one. CH = ch.toUpperCase(); } if (KEY == undefined && key != undefined) { // Most keys have identically key codes for both lowercase and uppercase // keypresses. Normally, only function keys would have distinct key codes, // whereas regular keys have character codes. KEY = key; } else if (KEY == undefined && CH != undefined) { // For regular keys, copy the character code to the key code. KEY = CH.charCodeAt(0); } if (key == undefined && ch != undefined) { // For regular keys, copy the character code to the key code. key = ch.charCodeAt(0); } // Convert characters to numeric character codes. If the character code // is undefined (i.e. this is a function key), set it to zero. ch = ch ? ch.charCodeAt(0) : 0; CH = CH ? CH.charCodeAt(0) : 0; // Mouse down events high light the key. We also set lastSelectedKey. This // is needed to that mouseout/mouseover can keep track of the key that // is currently being clicked. this.addListener(elem, 'mousedown', function(vt100, elem, key) { return function(e) { if ((e.which || e.button) == 1) { if (vt100.lastSelectedKey) { vt100.lastSelectedKey.className= ''; } // Highlight the key while the mouse button is held down. if (key == 16 /* Shift */) { if (!elem.className != vt100.isShift) { vt100.showShiftState(!vt100.isShift); } } else if (key == 17 /* Ctrl */) { if (!elem.className != vt100.isCtrl) { vt100.showCtrlState(!vt100.isCtrl); } } else if (key == 18 /* Alt */) { if (!elem.className != vt100.isAlt) { vt100.showAltState(!vt100.isAlt); } } else { elem.className = 'selected'; } vt100.lastSelectedKey = elem; } return false; }; }(this, elem, key)); var clicked = // Modifier keys update the state of the keyboard, but do not generate // any key clicks that get forwarded to the application. key >= 16 /* Shift */ && key <= 18 /* Alt */ ? function(vt100, elem) { return function(e) { if (elem == vt100.lastSelectedKey) { if (key == 16 /* Shift */) { // The user clicked the Shift key vt100.isShift = !vt100.isShift; vt100.showShiftState(vt100.isShift); } else if (key == 17 /* Ctrl */) { vt100.isCtrl = !vt100.isCtrl; vt100.showCtrlState(vt100.isCtrl); } else if (key == 18 /* Alt */) { vt100.isAlt = !vt100.isAlt; vt100.showAltState(vt100.isAlt); } vt100.lastSelectedKey = undefined; } if (vt100.lastSelectedKey) { vt100.lastSelectedKey.className = ''; vt100.lastSelectedKey = undefined; } return false; }; }(this, elem) : // Regular keys generate key clicks, when the mouse button is released or // when a mouse click event is received. function(vt100, elem, ch, key, CH, KEY) { return function(e) { if (vt100.lastSelectedKey) { if (elem == vt100.lastSelectedKey) { // The user clicked a key. if (vt100.isShift) { vt100.clickedKeyboard(e, elem, CH, KEY, true, vt100.isCtrl, vt100.isAlt); } else { vt100.clickedKeyboard(e, elem, ch, key, false, vt100.isCtrl, vt100.isAlt); } vt100.isShift = false; vt100.showShiftState(false); vt100.isCtrl = false; vt100.showCtrlState(false); vt100.isAlt = false; vt100.showAltState(false); } vt100.lastSelectedKey.className = ''; vt100.lastSelectedKey = undefined; } elem.className = ''; return false; }; }(this, elem, ch, key, CH, KEY); this.addListener(elem, 'mouseup', clicked); this.addListener(elem, 'click', clicked); // When moving the mouse away from a key, check if any keys need to be // deselected. this.addListener(elem, 'mouseout', function(vt100, elem, key) { return function(e) { if (key == 16 /* Shift */) { if (!elem.className == vt100.isShift) { vt100.showShiftState(vt100.isShift); } } else if (key == 17 /* Ctrl */) { if (!elem.className == vt100.isCtrl) { vt100.showCtrlState(vt100.isCtrl); } } else if (key == 18 /* Alt */) { if (!elem.className == vt100.isAlt) { vt100.showAltState(vt100.isAlt); } } else if (elem.className) { elem.className = ''; vt100.lastSelectedKey = elem; } else if (vt100.lastSelectedKey) { vt100.resetLastSelectedKey(e); } return false; }; }(this, elem, key)); // When moving the mouse over a key, select it if the user is still holding // the mouse button down (i.e. elem == lastSelectedKey) this.addListener(elem, 'mouseover', function(vt100, elem, key) { return function(e) { if (elem == vt100.lastSelectedKey) { if (key == 16 /* Shift */) { if (!elem.className != vt100.isShift) { vt100.showShiftState(!vt100.isShift); } } else if (key == 17 /* Ctrl */) { if (!elem.className != vt100.isCtrl) { vt100.showCtrlState(!vt100.isCtrl); } } else if (key == 18 /* Alt */) { if (!elem.className != vt100.isAlt) { vt100.showAltState(!vt100.isAlt); } } else if (!elem.className) { elem.className = 'selected'; } } else { vt100.resetLastSelectedKey(e); } return false; }; }(this, elem, key)); }; VT100.prototype.initializeKeyBindings = function(elem) { if (elem) { if (elem.nodeName == "I" || elem.nodeName == "B") { if (elem.id) { // Function keys. The Javascript keycode is part of the "id" var i = parseInt(elem.id); if (i) { // If the id does not parse as a number, it is not a keycode. this.addKeyBinding(elem, undefined, i); } } else { var child = elem.firstChild; if (child) { if (child.nodeName == "#text") { // If the key only has a text node as a child, then it is a letter. // Automatically compute the lower and upper case version of the // key. var text = this.getTextContent(child) || this.getTextContent(elem); this.addKeyBinding(elem, text.toLowerCase()); } else if (child.nextSibling) { // If the key has two children, they are the lower and upper case // character code, respectively. this.addKeyBinding(elem, this.getTextContent(child), undefined, this.getTextContent(child.nextSibling)); } } } } } // Recursively parse all other child nodes. for (elem = elem.firstChild; elem; elem = elem.nextSibling) { this.initializeKeyBindings(elem); } }; VT100.prototype.initializeKeyboardButton = function() { // Configure mouse event handlers for button that displays/hides keyboard this.addListener(this.keyboardImage, 'click', function(vt100) { return function(e) { if (vt100.keyboard.style.display != '') { if (vt100.reconnectBtn.style.visibility != '') { vt100.initializeKeyboard(); vt100.showSoftKeyboard(); } } else { vt100.hideSoftKeyboard(); vt100.input.focus(); } return false; }; }(this)); // Enable button that displays keyboard if (this.softKeyboard) { this.keyboardImage.style.visibility = 'visible'; } }; VT100.prototype.initializeKeyboard = function() { // Only need to initialize the keyboard the very first time. When doing so, // copy the keyboard layout from the iframe. if (this.keyboard.firstChild) { return; } this.keyboard.innerHTML = this.layout.contentDocument.body.innerHTML; var box = this.keyboard.firstChild; this.hideSoftKeyboard(); // Configure mouse event handlers for on-screen keyboard this.addListener(this.keyboard, 'click', function(vt100) { return function(e) { vt100.hideSoftKeyboard(); vt100.input.focus(); return false; }; }(this)); this.addListener(this.keyboard, 'selectstart', this.cancelEvent); this.addListener(box, 'click', this.cancelEvent); this.addListener(box, 'mouseup', function(vt100) { return function(e) { if (vt100.lastSelectedKey) { vt100.lastSelectedKey.className = ''; vt100.lastSelectedKey = undefined; } return false; }; }(this)); this.addListener(box, 'mouseout', function(vt100) { return function(e) { return vt100.resetLastSelectedKey(e); }; }(this)); this.addListener(box, 'mouseover', function(vt100) { return function(e) { return vt100.resetLastSelectedKey(e); }; }(this)); // Configure SHIFT key behavior var style = document.createElement('style'); var id = document.createAttribute('id'); id.nodeValue = 'shift_state'; style.setAttributeNode(id); var type = document.createAttribute('type'); type.nodeValue = 'text/css'; style.setAttributeNode(type); document.getElementsByTagName('head')[0].appendChild(style); // Set up key bindings this.initializeKeyBindings(box); }; 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, 'reconnect') || !this.getChildById(this.container, 'menu') || !this.getChildById(this.container, 'keyboard') || !this.getChildById(this.container, 'kbd_button') || !this.getChildById(this.container, 'kbd_img') || !this.getChildById(this.container, 'layout') || !this.getChildById(this.container, 'scrollable') || !this.getChildById(this.container, 'console') || !this.getChildById(this.container, 'alt_console') || !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, we'd rather 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 = '' + '' + '' + '
    ' + '
    ' + '
    ' + '' + '' + '' + '' + '
         
    ' + '
     
    ' + '
    ' +
                               '
    ' +
                               '
     
    ' + '
    ' + '' + '
    ' + '
     
    ' + '
    ' + '