7ab3b32465
different style sheet options. This code is still incomplete and subject to change (e.g. the command line syntax might still change). But it is good enough to demonstrate the concept on simple style sheets (such as selecting between normal and reverse video). git-svn-id: https://shellinabox.googlecode.com/svn/trunk@165 0da03de8-d603-11dd-86c2-0f8696b7b6f9
3481 lines
132 KiB
JavaScript
3481 lines
132 KiB
JavaScript
// VT100.js -- JavaScript based terminal emulator
|
|
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
|
|
//
|
|
// 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.initializeElements(container);
|
|
this.initializeAnsiColors();
|
|
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 = 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.mouseReporting = false;
|
|
this.utfEnabled = true;
|
|
this.visualBell = typeof suppressAllAudio !=
|
|
'undefined' &&
|
|
suppressAllAudio;
|
|
this.utfCount = 0;
|
|
this.utfChar = 0;
|
|
this.style = '';
|
|
this.attr = 0x00F0 /* 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);
|
|
this.gotoXY(0, 0);
|
|
this.showCursor();
|
|
this.isInverted = false;
|
|
this.refreshInvertedState();
|
|
this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.style);
|
|
};
|
|
|
|
VT100.prototype.initializeAnsiColors = function() {
|
|
var elem = document.createElement('pre');
|
|
this.container.appendChild(elem);
|
|
this.setTextContent(elem, ' ');
|
|
this.ansi = [ ];
|
|
for (var i = 0; i < 16; i++) {
|
|
elem.id = 'ansi' + i;
|
|
this.ansi[i] = this.getCurrentComputedStyle(elem, 'backgroundColor');
|
|
}
|
|
this.container.removeChild(elem);
|
|
};
|
|
|
|
VT100.prototype.addListener = function(elem, event, listener) {
|
|
if (elem.addEventListener) {
|
|
elem.addEventListener(event, listener, false);
|
|
} else {
|
|
elem.attachEvent('on' + event, listener);
|
|
}
|
|
};
|
|
|
|
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 "<hr />" before we can add the last group to the menu.
|
|
menu += '<hr />';
|
|
}
|
|
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];
|
|
label.innerHTML =
|
|
label.innerHTML.replace(/^\u2714 /, '');
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
entry = entry.nextSibling;
|
|
}
|
|
};
|
|
}(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 "<hr />" on
|
|
// both ends), or whether this is a on/off toggle, which can be grouped
|
|
// together with other on/off options.
|
|
group +=
|
|
'<li>' + (enabled ? '✔ ' : '') + label + '</li>';
|
|
}
|
|
this.usercss.innerHTML = menu;
|
|
}
|
|
};
|
|
|
|
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, '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') ||
|
|
!this.getChildById(this.container, 'attrib')) {
|
|
// 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 ? "" :
|
|
'<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
|
|
'id="beep_embed" ' +
|
|
'src="beep.wav" ' +
|
|
'autostart="false" ' +
|
|
'volume="100" ' +
|
|
'enablejavascript="true" ' +
|
|
'type="audio/x-wav" ' +
|
|
'height="16" ' +
|
|
'width="200" ' +
|
|
'style="position:absolute;left:-1000px;top:-1000px" />';
|
|
}
|
|
} catch (e) {
|
|
}
|
|
|
|
this.container.innerHTML =
|
|
'<div id="reconnect" style="visibility: hidden">' +
|
|
'<input type="button" value="Connect" ' +
|
|
'onsubmit="return false" />' +
|
|
'</div>' +
|
|
'<div id="cursize" style="visibility: hidden">' +
|
|
'</div>' +
|
|
'<div id="menu"></div>' +
|
|
'<div id="scrollable">' +
|
|
'<pre id="lineheight"> </pre>' +
|
|
'<pre id="console">' +
|
|
'<pre></pre>' +
|
|
'<div id="ieprobe"><span> </span></div>' +
|
|
'</pre>' +
|
|
'<pre id="alt_console" style="display: none"></pre>' +
|
|
'<div id="padding"></div>' +
|
|
'<pre id="cursor"> </pre>' +
|
|
'</div>' +
|
|
'<div class="hidden">' +
|
|
'<div id="usercss"></div>' +
|
|
'<pre><div><span id="space"></span></div></pre>' +
|
|
'<input type="textfield" id="input" />' +
|
|
'<input type="textfield" id="cliphelper" />' +
|
|
'<span id="attrib"> </span>' +
|
|
(typeof suppressAllAudio != 'undefined' &&
|
|
suppressAllAudio ? "" :
|
|
embed + '<bgsound id="beep_bgsound" loop=1 />') +
|
|
'</div>';
|
|
}
|
|
|
|
// 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 console and the
|
|
// cursor.
|
|
this.reconnectBtn = this.getChildById(this.container,'reconnect');
|
|
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.console =
|
|
[ this.getChildById(this.container, 'console'),
|
|
this.getChildById(this.container, 'alt_console') ];
|
|
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');
|
|
this.attributeHelper = this.getChildById(this.container, 'attrib');
|
|
|
|
// Add any user selectable style sheets to the menu
|
|
this.initializeUserCSSStyles();
|
|
|
|
// 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;
|
|
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.console.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;
|
|
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();
|
|
vt100.showCurrentSize();
|
|
}
|
|
}(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 listener to reconnect button
|
|
this.addListener(this.reconnectBtn.firstChild, 'click',
|
|
function(vt100) {
|
|
return function() {
|
|
var rc = vt100.reconnect();
|
|
vt100.input.focus();
|
|
return rc;
|
|
}
|
|
}(this));
|
|
|
|
// 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 <input> 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.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.reconnect = function() {
|
|
return false;
|
|
};
|
|
|
|
VT100.prototype.showReconnect = function(state) {
|
|
if (state) {
|
|
this.reconnectBtn.style.visibility = '';
|
|
} else {
|
|
this.reconnectBtn.style.visibility = 'hidden';
|
|
}
|
|
};
|
|
|
|
VT100.prototype.repairElements = function(console) {
|
|
for (var line = console.firstChild; line; line = line.nextSibling) {
|
|
if (!line.clientHeight) {
|
|
var newLine = document.createElement(line.tagName);
|
|
newLine.style.cssText = line.style.cssText;
|
|
newLine.className = line.className;
|
|
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;
|
|
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() {
|
|
// The 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.console[0]);
|
|
this.repairElements(this.console[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 console = this.console[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) {
|
|
console.removeChild(console.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);
|
|
this.scrollable.scrollTop = this.numScrollbackLines *
|
|
this.cursorHeight + 1;
|
|
|
|
// Update classNames for lines in the scrollback buffer
|
|
var line = console.firstChild;
|
|
for (var i = 0; i < this.numScrollbackLines; i++) {
|
|
line.className = 'scrollback';
|
|
line = line.nextSibling;
|
|
}
|
|
while (line) {
|
|
line.className = '';
|
|
line = line.nextSibling;
|
|
}
|
|
|
|
// Reposition the reconnect button
|
|
this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
|
|
this.reconnectBtn.clientWidth)/2 + 'px';
|
|
this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
|
|
this.reconnectBtn.clientHeight)/2 + 'px';
|
|
|
|
// Send notification that the window size has been changed
|
|
this.resized(this.terminalWidth, this.terminalHeight);
|
|
};
|
|
|
|
VT100.prototype.showCurrentSize = function() {
|
|
if (!this.indicateSize) {
|
|
return;
|
|
}
|
|
this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
|
|
this.terminalHeight;
|
|
this.curSizeBox.style.left =
|
|
(this.terminalWidth*this.cursorWidth -
|
|
this.curSizeBox.clientWidth)/2 + 'px';
|
|
this.curSizeBox.style.top =
|
|
(this.terminalHeight*this.cursorHeight -
|
|
this.curSizeBox.clientHeight)/2 + 'px';
|
|
this.curSizeBox.style.visibility = '';
|
|
if (this.curSizeTimeout) {
|
|
clearTimeout(this.curSizeTimeout);
|
|
}
|
|
|
|
// Only show the terminal size for a short amount of time after resizing.
|
|
// Then hide this information, again. Some browsers generate resize events
|
|
// throughout the entire resize operation. This is nice, and we will show
|
|
// the terminal size while the user is dragging the window borders.
|
|
// Other browsers only generate a single event when the user releases the
|
|
// mouse. In those cases, we can only show the terminal size once at the
|
|
// end of the resize operation.
|
|
this.curSizeTimeout = setTimeout(function(vt100) {
|
|
return function() {
|
|
vt100.curSizeTimeout = null;
|
|
vt100.curSizeBox.style.visibility = 'hidden';
|
|
};
|
|
}(this), 1000);
|
|
};
|
|
|
|
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) {
|
|
try {
|
|
// For non-IE browsers
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
} catch (e) {
|
|
}
|
|
try {
|
|
// For IE
|
|
event.cancelBubble = true;
|
|
event.returnValue = false;
|
|
event.button = 0;
|
|
event.keyCode = 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;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
var report = '\u001B[M' + String.fromCharCode(button + 32) +
|
|
String.fromCharCode(x + 33) +
|
|
String.fromCharCode(y + 33);
|
|
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) {
|
|
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) {
|
|
return elem.textContent ||
|
|
(typeof elem.textContent == 'undefined' ? elem.innerText : '');
|
|
};
|
|
|
|
VT100.prototype.setTextContent = function(elem, s) {
|
|
// Check if we find any URLs in the text. If so, automatically convert them
|
|
// to links.
|
|
if (this.urlRE && this.urlRE.test(s)) {
|
|
var inner = '';
|
|
for (;;) {
|
|
var consumed = 0;
|
|
if (RegExp.leftContext != null) {
|
|
inner += this.htmlEscape(RegExp.leftContext);
|
|
consumed += RegExp.leftContext.length;
|
|
}
|
|
var url = this.htmlEscape(RegExp.lastMatch);
|
|
var fullUrl = url;
|
|
|
|
// If no protocol was specified, try to guess a reasonable one.
|
|
if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
|
|
url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
|
|
var slash = url.indexOf('/');
|
|
var at = url.indexOf('@');
|
|
var question = url.indexOf('?');
|
|
if (at > 0 &&
|
|
(at < question || question < 0) &&
|
|
(slash < 0 || (question > 0 && slash > question))) {
|
|
fullUrl = 'mailto:' + url;
|
|
} else {
|
|
fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
|
|
url;
|
|
}
|
|
}
|
|
|
|
inner += '<a target="vt100Link" href="' + fullUrl +
|
|
'">' + url + '</a>';
|
|
consumed += RegExp.lastMatch.length;
|
|
s = s.substr(consumed);
|
|
if (!this.urlRE.test(s)) {
|
|
if (RegExp.rightContext != null) {
|
|
inner += this.htmlEscape(RegExp.rightContext);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
elem.innerHTML = inner;
|
|
return;
|
|
}
|
|
|
|
// Updating the content of an element is an expensive operation. It actually
|
|
// pays off to first check whether the element is still unchanged.
|
|
if (typeof elem.textContent == 'undefined') {
|
|
if (elem.innerText != s) {
|
|
try {
|
|
elem.innerText = s;
|
|
} catch (e) {
|
|
// Very old versions of IE do not allow setting innerText. Instead,
|
|
// remove all children, by setting innerHTML and then set the text
|
|
// using DOM methods.
|
|
elem.innerHTML = '';
|
|
elem.appendChild(document.createTextNode(
|
|
this.replaceChar(s, ' ', '\u00A0')));
|
|
}
|
|
}
|
|
} else {
|
|
if (elem.textContent != s) {
|
|
elem.textContent = s;
|
|
}
|
|
}
|
|
};
|
|
|
|
VT100.prototype.insertBlankLine = function(y, style) {
|
|
// 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.
|
|
if (style == undefined) {
|
|
style = '';
|
|
}
|
|
var line;
|
|
if (!style) {
|
|
line = document.createElement('pre');
|
|
this.setTextContent(line, '\n');
|
|
} else {
|
|
line = document.createElement('div');
|
|
var span = document.createElement('span');
|
|
span.style.cssText = style;
|
|
this.setTextContent(span, this.spaces(this.terminalWidth));
|
|
line.appendChild(span);
|
|
}
|
|
line.style.height = this.cursorHeight + 'px';
|
|
var console = this.console[this.currentScreen];
|
|
if (console.childNodes.length > y) {
|
|
console.insertBefore(line, console.childNodes[y]);
|
|
} else {
|
|
console.appendChild(line);
|
|
}
|
|
};
|
|
|
|
VT100.prototype.updateWidth = function() {
|
|
this.terminalWidth = Math.floor(this.console[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
|
|
// <div> which is embededded somewhere in the web page.
|
|
if (this.isEmbedded) {
|
|
// Embedded terminal. Use size of the containing <div> (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.console[this.currentScreen].offsetHeight /
|
|
this.cursorHeight) -
|
|
this.terminalHeight;
|
|
this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
|
|
return this.numScrollbackLines;
|
|
};
|
|
|
|
VT100.prototype.truncateLines = function(width) {
|
|
if (width < 0) {
|
|
width = 0;
|
|
}
|
|
for (var line = this.console[this.currentScreen].firstChild; line;
|
|
line = line.nextSibling) {
|
|
if (line.tagName == 'DIV') {
|
|
var x = 0;
|
|
|
|
// Traverse current line and truncate it once we saw "width" characters
|
|
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;
|
|
}
|
|
// Prune white space from the end of the current line
|
|
var span = line.lastChild;
|
|
while (span && !span.style.cssText.length) {
|
|
// Scan backwards looking for first non-space character
|
|
var s = this.getTextContent(span);
|
|
for (var i = s.length; i--; ) {
|
|
if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
|
|
if (i+1 != s.length) {
|
|
this.setTextContent(s.substr(0, i+1));
|
|
}
|
|
span = null;
|
|
break;
|
|
}
|
|
}
|
|
if (span) {
|
|
var sibling = span;
|
|
span = span.previousSibling;
|
|
if (span) {
|
|
// Remove blank <span>'s from end of line
|
|
line.removeChild(sibling);
|
|
} else {
|
|
// Remove entire line (i.e. <div>), if empty
|
|
var blank = document.createElement('pre');
|
|
blank.style.height = this.cursorHeight + 'px';
|
|
this.setTextContent(blank, '\n');
|
|
line.parentNode.replaceChild(blank, line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
VT100.prototype.putString = function(x, y, text, style) {
|
|
if (!style) {
|
|
style = '';
|
|
}
|
|
var yIdx = y + this.numScrollbackLines;
|
|
var line;
|
|
var sibling;
|
|
var s;
|
|
var span;
|
|
var xPos = 0;
|
|
var console = this.console[this.currentScreen];
|
|
if (!text.length && (yIdx >= console.childNodes.length ||
|
|
console.childNodes[yIdx].tagName != 'DIV')) {
|
|
// Positioning cursor to a blank location
|
|
span = null;
|
|
} else {
|
|
// Create missing blank lines at end of page
|
|
while (console.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 <div> containing one or more
|
|
// <span>s. For blank lines, this fails as browsers tend to optimize them
|
|
// away. But fortunately, a <pre> 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 = console.childNodes[yIdx];
|
|
|
|
// If necessary, promote blank '\n' line to a <div> tag
|
|
if (line.tagName != 'DIV') {
|
|
var div = document.createElement('div');
|
|
div.style.height = this.cursorHeight + 'px';
|
|
div.innerHTML = '<span></span>';
|
|
console.replaceChild(div, line);
|
|
line = div;
|
|
}
|
|
|
|
// Scan through list of <span>'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 <span> is not long enough, pad with spaces or add new
|
|
// span
|
|
s = this.getTextContent(span);
|
|
var oldStyle = span.style.cssText;
|
|
if (xPos + s.length < x) {
|
|
if (oldStyle != '') {
|
|
span = document.createElement('span');
|
|
line.appendChild(span);
|
|
span.style.cssText = '';
|
|
oldStyle = '';
|
|
xPos += s.length;
|
|
s = '';
|
|
}
|
|
do {
|
|
s += ' ';
|
|
} while (xPos + s.length < x);
|
|
}
|
|
|
|
// If styles do not match, create a new <span>
|
|
var del = text.length - s.length + x - xPos;
|
|
if (oldStyle != style && (oldStyle || style)) {
|
|
if (xPos == x) {
|
|
// Replacing text at beginning of existing <span>
|
|
if (text.length >= s.length) {
|
|
// New text is equal or longer than existing text
|
|
s = text;
|
|
} else {
|
|
// Insert new <span> before the current one, then remove leading
|
|
// part of existing <span>, adjust style of new <span>, and finally
|
|
// set its contents
|
|
sibling = document.createElement('span');
|
|
line.insertBefore(sibling, span);
|
|
this.setTextContent(span, s.substr(text.length));
|
|
span = sibling;
|
|
s = text;
|
|
}
|
|
} else {
|
|
// Replacing text some way into the existing <span>
|
|
var remainder = s.substr(x + text.length - xPos);
|
|
this.setTextContent(span, s.substr(0, x - xPos));
|
|
xPos = x;
|
|
sibling = document.createElement('span');
|
|
if (span.nextSibling) {
|
|
line.insertBefore(sibling, span.nextSibling);
|
|
span = sibling;
|
|
if (remainder.length) {
|
|
sibling = document.createElement('span');
|
|
sibling.style.cssText = oldStyle;
|
|
this.setTextContent(sibling, remainder);
|
|
line.insertBefore(sibling, span.nextSibling);
|
|
}
|
|
} else {
|
|
line.appendChild(sibling);
|
|
span = sibling;
|
|
if (remainder.length) {
|
|
sibling = document.createElement('span');
|
|
sibling.style.cssText = oldStyle;
|
|
this.setTextContent(sibling, remainder);
|
|
line.appendChild(sibling);
|
|
}
|
|
}
|
|
s = text;
|
|
}
|
|
span.style.cssText = style;
|
|
} else {
|
|
// Overwrite (partial) <span> with new text
|
|
s = s.substr(0, x - xPos) +
|
|
text +
|
|
s.substr(x + text.length - xPos);
|
|
}
|
|
this.setTextContent(span, s);
|
|
|
|
|
|
// Delete all subsequent <span>'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 <span> with next sibling, if styles are identical
|
|
if (sibling && span.style.cssText == sibling.style.cssText) {
|
|
this.setTextContent(span,
|
|
this.getTextContent(span) +
|
|
this.getTextContent(sibling));
|
|
line.removeChild(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.
|
|
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 +
|
|
console.offsetLeft + 'px';
|
|
}
|
|
this.cursorY = yIdx - this.numScrollbackLines;
|
|
if (pixelY >= 0) {
|
|
this.cursor.style.top = pixelY + 'px';
|
|
} else {
|
|
this.cursor.style.top = yIdx*this.cursorHeight +
|
|
console.offsetTop + 'px';
|
|
}
|
|
|
|
if (text.length) {
|
|
// Merge <span> with previous sibling, if styles are identical
|
|
if ((sibling = span.previousSibling) &&
|
|
span.style.cssText == sibling.style.cssText) {
|
|
this.setTextContent(span,
|
|
this.getTextContent(sibling) +
|
|
this.getTextContent(span));
|
|
line.removeChild(sibling);
|
|
}
|
|
|
|
// Prune white space from the end of the current line
|
|
span = line.lastChild;
|
|
while (span && !span.style.cssText.length) {
|
|
// Scan backwards looking for first non-space character
|
|
s = this.getTextContent(span);
|
|
for (var i = s.length; i--; ) {
|
|
if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
|
|
if (i+1 != s.length) {
|
|
this.setTextContent(s.substr(0, i+1));
|
|
}
|
|
span = null;
|
|
break;
|
|
}
|
|
}
|
|
if (span) {
|
|
sibling = span;
|
|
span = span.previousSibling;
|
|
if (span) {
|
|
// Remove blank <span>'s from end of line
|
|
line.removeChild(sibling);
|
|
} else {
|
|
// Remove entire line (i.e. <div>), if empty
|
|
var blank = document.createElement('pre');
|
|
blank.style.height = this.cursorHeight + 'px';
|
|
this.setTextContent(blank, '\n');
|
|
line.parentNode.replaceChild(blank, line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
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);
|
|
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.scrollable.style.color = this.ansi[15];
|
|
this.scrollable.style.backgroundColor = this.ansi[0];
|
|
} else {
|
|
this.scrollable.style.color = '';
|
|
this.scrollable.style.backgroundColor = '';
|
|
}
|
|
};
|
|
|
|
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.console[1-this.currentScreen].style.display = 'none';
|
|
this.console[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);
|
|
} 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);
|
|
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;
|
|
};
|
|
|
|
VT100.prototype.spaces = function(i) {
|
|
var s = '';
|
|
while (i-- > 0) {
|
|
s += ' ';
|
|
}
|
|
return s;
|
|
};
|
|
|
|
VT100.prototype.clearRegion = function(x, y, w, h, 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;
|
|
}
|
|
|
|
// 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 (!this.numScrollbackLines &&
|
|
w == this.terminalWidth && h == this.terminalHeight &&
|
|
!style) {
|
|
var console = this.console[this.currentScreen];
|
|
while (console.lastChild) {
|
|
console.removeChild(console.lastChild);
|
|
}
|
|
this.putString(this.cursorX, this.cursorY, '', undefined);
|
|
} else {
|
|
var hidden = this.hideCursor();
|
|
var cx = this.cursorX;
|
|
var cy = this.cursorY;
|
|
var s = this.spaces(w);
|
|
for (var i = y+h; i-- > y; ) {
|
|
this.putString(x, i, s, style);
|
|
}
|
|
hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
|
|
}
|
|
};
|
|
|
|
VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
|
|
var text = [ ];
|
|
var style = [ ];
|
|
var console = this.console[this.currentScreen];
|
|
if (sY >= console.childNodes.length) {
|
|
text[0] = this.spaces(w);
|
|
style[0] = null;
|
|
} else {
|
|
var line = console.childNodes[sY];
|
|
if (line.tagName != 'DIV' || !line.childNodes.length) {
|
|
text[0] = this.spaces(w);
|
|
style[0] = null;
|
|
} 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);
|
|
style[style.length] = span.style.cssText;
|
|
w -= len - o;
|
|
}
|
|
x += len;
|
|
}
|
|
if (w > 0) {
|
|
text[text.length] = this.spaces(w);
|
|
style[style.length] = null;
|
|
}
|
|
}
|
|
}
|
|
var hidden = this.hideCursor();
|
|
var cx = this.cursorX;
|
|
var cy = this.cursorY;
|
|
for (var i = 0; i < text.length; i++) {
|
|
this.putString(dX, dY - this.numScrollbackLines, text[i], style[i]);
|
|
dX += text[i].length;
|
|
}
|
|
hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
|
|
};
|
|
|
|
VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY, 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.
|
|
this.attributeHelper.cssText
|
|
= style.replace(/text-decoration:underline;/, "");
|
|
style = this.attributeHelper.cssText;
|
|
}
|
|
|
|
// 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 console = this.console[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 console,
|
|
// as the terminal is high
|
|
while (console.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(console.childNodes.length, style);
|
|
}
|
|
|
|
// Adjust the number of lines in the scrollback buffer by
|
|
// removing excess entries.
|
|
this.updateNumScrollbackLines();
|
|
while (this.numScrollbackLines >
|
|
(this.currentScreen ? 0 : this.maxScrollbackLines)) {
|
|
console.removeChild(console.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; ) {
|
|
console.childNodes[i].className = 'scrollback';
|
|
}
|
|
} else {
|
|
// Scrolling up without adding to the scrollback buffer.
|
|
for (var i = -incY;
|
|
i-- > 0 &&
|
|
console.childNodes.length >
|
|
this.numScrollbackLines + y + incY; ) {
|
|
console.removeChild(console.childNodes[
|
|
this.numScrollbackLines + y + incY]);
|
|
}
|
|
|
|
// If we used to have a scrollback buffer, then 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 ||
|
|
console.childNodes.length > this.numScrollbackLines+y+h+incY) {
|
|
for (var i = -incY; i-- > 0; ) {
|
|
this.insertBlankLine(this.numScrollbackLines + y + h + incY,
|
|
style);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Scrolling down
|
|
for (var i = incY;
|
|
i-- > 0 &&
|
|
console.childNodes.length > this.numScrollbackLines + y + h; ) {
|
|
console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
|
|
}
|
|
for (var i = incY; i--; ) {
|
|
this.insertBlankLine(this.numScrollbackLines + y, 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, style);
|
|
} else if (incX < 0) {
|
|
this.clearRegion(x + w + incX, y, -incX, h, style);
|
|
}
|
|
if (incY > 0) {
|
|
this.clearRegion(x, y, w, incY, style);
|
|
} else if (incY < 0) {
|
|
this.clearRegion(x, y + h + incY, w, -incY, 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);
|
|
}
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
VT100.prototype.toggleUTF = function() {
|
|
this.utfEnabled = !this.utfEnabled;
|
|
};
|
|
|
|
VT100.prototype.toggleBell = function() {
|
|
this.visualBell = !this.visualBell;
|
|
};
|
|
|
|
VT100.prototype.about = function() {
|
|
alert("VT100 Terminal Emulator " + "2.9 (revision 165)" +
|
|
"\nCopyright 2008-2009 by Markus Gutschke\n" +
|
|
"For more information check http://shellinabox.com");
|
|
};
|
|
|
|
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.extendContextMenu = function(entries, actions) {
|
|
};
|
|
|
|
VT100.prototype.showContextMenu = function(x, y) {
|
|
this.menu.innerHTML =
|
|
'<table class="popup" ' +
|
|
'cellpadding="0" cellspacing="0">' +
|
|
'<tr><td>' +
|
|
'<ul id="menuentries">' +
|
|
'<li id="beginclipboard">Copy</li>' +
|
|
'<li id="endclipboard">Paste</li>' +
|
|
'<hr />' +
|
|
'<li id="reset">Reset</li>' +
|
|
'<hr />' +
|
|
'<li id="beginconfig">' +
|
|
(this.utfEnabled ? '✔ ' : '') + 'Unicode</li>' +
|
|
'<li id="endconfig">' +
|
|
(this.visualBell ? '✔ ' : '') + 'Visual Bell</li>'+
|
|
(this.usercss.firstChild ?
|
|
'<hr id="beginusercss" />' +
|
|
this.usercss.innerHTML +
|
|
'<hr id="endusercss" />' :
|
|
'<hr />') +
|
|
'<li id="about">About...</li>' +
|
|
'</ul>' +
|
|
'</td></tr>' +
|
|
'</table>';
|
|
|
|
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.firstChild.className
|
|
= 'disabled';
|
|
}
|
|
var p = this.pasteFnc();
|
|
if (!p) {
|
|
menuentries.childNodes[1].className
|
|
= 'disabled';
|
|
}
|
|
|
|
// Actions for default items
|
|
var actions = [ this.copyLast, p, this.reset,
|
|
this.toggleUTF, this.toggleBell ];
|
|
|
|
// Actions for user CSS styles (if any)
|
|
for (var i = 0; i < this.usercssActions.length; ++i) {
|
|
actions[actions.length] = this.usercssActions[i];
|
|
}
|
|
actions[actions.length] = this.about;
|
|
|
|
// Allow subclasses to dynamically add entries to the context menu
|
|
this.extendContextMenu(menuentries, actions);
|
|
|
|
// Hook up event listeners
|
|
for (var node = menuentries.firstChild, i = 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);
|
|
return vt100.cancelEvent(event || window.event);
|
|
}
|
|
}(this, actions[i]));
|
|
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);
|
|
}
|
|
}());
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// 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 = '';
|
|
};
|
|
|
|
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.handleKey = function(event) {
|
|
var ch, key;
|
|
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;
|
|
}
|
|
|
|
// Apply modifier keys (ctrl and shift)
|
|
if (ch) {
|
|
key = undefined;
|
|
if (event.ctrlKey) {
|
|
if (ch >= 32 && ch <= 127) {
|
|
ch &= 0x1F;
|
|
}
|
|
} else {
|
|
if (event.shiftKey) {
|
|
if (ch >= 97 && ch <= 122) {
|
|
ch -= 32;
|
|
}
|
|
} else {
|
|
if (ch >= 65 && ch <= 90) {
|
|
ch += 32;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ch = undefined;
|
|
}
|
|
|
|
// 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) {
|
|
ch = String.fromCharCode(ch);
|
|
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 = '0'; break;
|
|
case 97: /* 1 */ ch = '1'; break;
|
|
case 98: /* 2 */ ch = '2'; break;
|
|
case 99: /* 3 */ ch = '3'; break;
|
|
case 100: /* 4 */ ch = '4'; break;
|
|
case 101: /* 5 */ ch = '5'; break;
|
|
case 102: /* 6 */ ch = '6'; break;
|
|
case 103: /* 7 */ ch = '7'; break;
|
|
case 104: /* 8 */ ch = '8'; break;
|
|
case 105: /* 9 */ ch = '9'; break;
|
|
case 106: /* * */ ch = '*'; break;
|
|
case 107: /* + */ ch = '+'; break;
|
|
case 109: /* - */ ch = '-'; break;
|
|
case 110: /* . */ ch = '.'; break;
|
|
case 111: /* / */ ch = '/'; 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 144: /* Num Lock */ return;
|
|
case 145: /* Scroll Lock */ return;
|
|
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.vt100('R: c=');
|
|
// for (var i = 0; i < ch.length; i++)
|
|
// this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
|
|
// this.vt100('\r\n');
|
|
this.keysPressed(ch);
|
|
}
|
|
};
|
|
|
|
VT100.prototype.inspect = function(o, d) {
|
|
if (d == undefined) {
|
|
d = 0;
|
|
}
|
|
var rc = '';
|
|
if (typeof o == 'object' && ++d < 2) {
|
|
rc = '[\r\n';
|
|
for (i in o) {
|
|
rc += this.spaces(d * 2) + i + ' -> ';
|
|
try {
|
|
rc += this.inspect(o[i], d);
|
|
} catch (e) {
|
|
rc += '?' + '?' + '?\r\n';
|
|
}
|
|
}
|
|
rc += ']\r\n';
|
|
} else {
|
|
rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
|
|
}
|
|
return rc;
|
|
};
|
|
|
|
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;
|
|
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;
|
|
return fake;
|
|
}
|
|
}
|
|
return event;
|
|
};
|
|
|
|
VT100.prototype.keyDown = function(event) {
|
|
// this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
|
|
// (event.shiftKey || event.ctrlKey || event.altKey ||
|
|
// event.metaKey ? ', ' +
|
|
// (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
|
|
// (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
|
|
// '\r\n');
|
|
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;
|
|
var normalKey =
|
|
alphNumKey ||
|
|
event.keyCode == 59 || event.keyCode == 61 ||
|
|
event.keyCode == 106 || event.keyCode == 107 ||
|
|
event.keyCode >= 109 && event.keyCode <= 111 ||
|
|
event.keyCode >= 186 && event.keyCode <= 192 ||
|
|
event.keyCode >= 219 && event.keyCode <= 222 ||
|
|
event.keyCode == 226 || event.keyCode == 252;
|
|
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;
|
|
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;
|
|
event.keyCode = 0;
|
|
} catch (e) {
|
|
}
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
VT100.prototype.keyPressed = function(event) {
|
|
// this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
|
|
// (event.shiftKey || event.ctrlKey || event.altKey ||
|
|
// event.metaKey ? ', ' +
|
|
// (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
|
|
// (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
|
|
// '\r\n');
|
|
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;
|
|
event.keyCode = 0;
|
|
} catch (e) {
|
|
}
|
|
|
|
this.lastNormalKeyDownEvent = undefined;
|
|
this.lastKeyPressedEvent = event;
|
|
return false;
|
|
};
|
|
|
|
VT100.prototype.keyUp = function(event) {
|
|
// this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
|
|
// (event.shiftKey || event.ctrlKey || event.altKey ||
|
|
// event.metaKey ? ', ' +
|
|
// (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
|
|
// (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
|
|
// '\r\n');
|
|
if (this.lastKeyPressedEvent) {
|
|
// The compose key on Linux occasionally confuses the browser and keeps
|
|
// inserting bogus characters into the input field, even if just a 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.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 >= 186 && event.keyCode <= 192 ||
|
|
event.keyCode >= 219 && event.keyCode <= 222 ||
|
|
event.keyCode == 252;
|
|
var fake = [ ];
|
|
fake.ctrlKey = event.ctrlKey;
|
|
fake.shiftKey = event.shiftKey;
|
|
fake.altKey = event.altKey;
|
|
fake.metaKey = event.metaKey;
|
|
if (asciiKey) {
|
|
fake.charCode = event.keyCode;
|
|
fake.keyCode = 0;
|
|
} else {
|
|
fake.charCode = 0;
|
|
fake.keyCode = event.keyCode;
|
|
if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
|
|
fake = this.fixEvent(fake);
|
|
}
|
|
}
|
|
this.lastNormalKeyDownEvent = undefined;
|
|
this.handleKey(fake);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// For IE
|
|
event.cancelBubble = true;
|
|
event.returnValue = false;
|
|
event.keyCode = 0;
|
|
} 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.flashScreen = function() {
|
|
this.isInverted = !this.isInverted;
|
|
this.refreshInvertedState();
|
|
this.isInverted = !this.isInverted;
|
|
setTimeout(function(vt100) {
|
|
return function() {
|
|
vt100.refreshInvertedState();
|
|
};
|
|
}(this), 100);
|
|
};
|
|
|
|
VT100.prototype.beep = function() {
|
|
if (this.visualBell) {
|
|
this.flashScreen();
|
|
} else {
|
|
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.style);
|
|
offset = undefined;
|
|
} 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.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() {
|
|
var style = '';
|
|
if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
|
|
style += 'text-decoration:underline;';
|
|
}
|
|
var bg = (this.attr >> 4) & 0xF;
|
|
var fg = this.attr & 0xF;
|
|
if (this.attr & 0x0100 /* ATTR_REVERSE */) {
|
|
var tmp = bg;
|
|
bg = fg;
|
|
fg = tmp;
|
|
}
|
|
if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
|
|
fg = 8; // Dark grey
|
|
} else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
|
|
fg |= 8;
|
|
}
|
|
if (this.attr & 0x1000 /* ATTR_BLINK */) {
|
|
bg ^= 8;
|
|
}
|
|
// Make some readability enhancements. Most notably, disallow identical
|
|
// background and foreground colors.
|
|
if (bg == fg) {
|
|
if ((fg ^= 8) == 7) {
|
|
fg = 8;
|
|
}
|
|
}
|
|
// And disallow bright colors on a light-grey background.
|
|
if (bg == 7 && fg >= 8) {
|
|
if ((fg -= 8) == 7) {
|
|
fg = 8;
|
|
}
|
|
}
|
|
|
|
if (fg != 0) {
|
|
style += 'color:' + this.ansi[fg] + ';';
|
|
}
|
|
if (bg != 15) {
|
|
style += 'background-color:' + this.ansi[bg] + ';';
|
|
}
|
|
this.attributeHelper.cssText = style;
|
|
this.style = this.attributeHelper.cssText;
|
|
};
|
|
|
|
VT100.prototype.setAttrColors = function(attr) {
|
|
if (attr != this.attr) {
|
|
this.attr = attr;
|
|
this.updateStyle();
|
|
}
|
|
};
|
|
|
|
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]);
|
|
};
|
|
|
|
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: /* Toggling between 80/132 mode is not implemented */ 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.csiAt = function(number) {
|
|
// Insert spaces
|
|
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.style);
|
|
this.needWrap = false;
|
|
};
|
|
|
|
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.style);
|
|
if (this.cursorY < this.terminalHeight-2) {
|
|
this.clearRegion(0, this.cursorY+1,
|
|
this.terminalWidth, this.terminalHeight-this.cursorY-1,
|
|
this.style);
|
|
}
|
|
break;
|
|
case 1: // Erase from start to cursor
|
|
if (this.cursorY > 0) {
|
|
this.clearRegion(0, 0,
|
|
this.terminalWidth, this.cursorY, this.style);
|
|
}
|
|
this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
|
|
break;
|
|
case 2: // Erase whole display
|
|
this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
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.style);
|
|
break;
|
|
case 1: // Erase from start of line to cursor
|
|
this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
|
|
break;
|
|
case 2: // Erase whole line
|
|
this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
needWrap = false;
|
|
};
|
|
|
|
VT100.prototype.csiL = function(number) {
|
|
// Open line by inserting blank line(s)
|
|
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.style);
|
|
needWrap = false;
|
|
};
|
|
|
|
VT100.prototype.csiM = function(number) {
|
|
// Delete line(s), scrolling up the bottom of the screen.
|
|
if (this.cursorY >= this.bottom) {
|
|
return;
|
|
}
|
|
if (number == 0) {
|
|
number = 1;
|
|
}
|
|
if (number > this.bottom - this.cursorY) {
|
|
number = bottom - cursorY;
|
|
}
|
|
this.scrollRegion(0, this.cursorY + number,
|
|
this.terminalWidth, this.bottom - this.cursorY - number,
|
|
0, -number, this.style);
|
|
needWrap = false;
|
|
};
|
|
|
|
VT100.prototype.csim = function() {
|
|
for (var i = 0; i <= this.npar; i++) {
|
|
switch (this.par[i]) {
|
|
case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; 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();
|
|
};
|
|
|
|
VT100.prototype.csiP = function(number) {
|
|
// Delete character(s) following cursor
|
|
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.style);
|
|
needWrap = false;
|
|
};
|
|
|
|
VT100.prototype.csiX = function(number) {
|
|
// Clear characters following cursor
|
|
if (number == 0) {
|
|
number++;
|
|
}
|
|
if (number > this.terminalWidth - this.cursorX) {
|
|
number = this.terminalWidth - this.cursorX;
|
|
}
|
|
this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
|
|
needWrap = false;
|
|
};
|
|
|
|
VT100.prototype.settermCommand = function() {
|
|
// Setterm commands are not implemented
|
|
};
|
|
|
|
VT100.prototype.doControl = function(ch) {
|
|
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;
|
|
/*7*/ case 0x37: this.saveCursor(); break;
|
|
/*8*/ case 0x38: this.restoreCursor(); break;
|
|
/*>*/ case 0x3E: this.applKeyMode = false; break;
|
|
/*=*/ case 0x3D: this.applKeyMode = true; break;
|
|
/*D*/ case 0x44: this.lf(); break;
|
|
/*E*/ case 0x45: this.cr(); this.lf(); break;
|
|
/*M*/ case 0x4D: this.ri(); break;
|
|
/*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
|
|
/*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
|
|
/*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
|
|
/*Z*/ case 0x5A: this.respondID(); break;
|
|
/*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
|
|
/*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
|
|
/*c*/ case 0x63: this.reset(); break;
|
|
/*g*/ case 0x67: this.flashScreen(); break;
|
|
default: break;
|
|
}
|
|
break;
|
|
case 15 /* ESnonstd */:
|
|
switch (ch) {
|
|
/*0*/ case 0x30:
|
|
/*1*/ case 0x31:
|
|
/*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
|
|
/*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
|
|
this.isEsc = 16 /* ESpalette */; break;
|
|
/*R*/ case 0x52: // 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) {
|
|
/*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
|
|
/*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
|
|
/*n*/ case 0x6E: /* disable key modifier resource values */ break;
|
|
/*p*/ case 0x70: /* 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) {
|
|
/*h*/ case 0x68: this.setMode(true); break;
|
|
/*l*/ case 0x6C: this.setMode(false); break;
|
|
/*c*/ case 0x63: 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 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
|
|
/*G*/ case 0x47:
|
|
/*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
|
|
/*A*/ case 0x41: this.gotoXY(this.cursorX,
|
|
this.cursorY - (this.par[0] ? this.par[0] : 1));
|
|
break;
|
|
/*B*/ case 0x42:
|
|
/*e*/ case 0x65: this.gotoXY(this.cursorX,
|
|
this.cursorY + (this.par[0] ? this.par[0] : 1));
|
|
break;
|
|
/*C*/ case 0x43:
|
|
/*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
|
|
this.cursorY); break;
|
|
/*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
|
|
this.cursorY); break;
|
|
/*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
|
|
break;
|
|
/*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
|
|
break;
|
|
/*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
|
|
/*H*/ case 0x48:
|
|
/*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
|
|
/*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
|
|
/*@*/ case 0x40: this.csiAt(this.par[0]); break;
|
|
/*J*/ case 0x4A: this.csiJ(this.par[0]); break;
|
|
/*K*/ case 0x4B: this.csiK(this.par[0]); break;
|
|
/*L*/ case 0x4C: this.csiL(this.par[0]); break;
|
|
/*M*/ case 0x4D: this.csiM(this.par[0]); break;
|
|
/*m*/ case 0x6D: this.csim(); break;
|
|
/*P*/ case 0x50: this.csiP(this.par[0]); break;
|
|
/*X*/ case 0x58: this.csiX(this.par[0]); break;
|
|
/*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
|
|
/*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
|
|
/*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
|
|
/*g*/ case 0x67: if (this.par[0] == 0) {
|
|
this.userTabStop[this.cursorX] = false;
|
|
} else if (this.par[0] == 2 || this.par[0] == 3) {
|
|
this.userTabStop = [ ];
|
|
for (var i = 0; i < this.terminalWidth; i++) {
|
|
this.userTabStop[i] = false;
|
|
}
|
|
}
|
|
break;
|
|
/*h*/ case 0x68: this.setMode(true); break;
|
|
/*l*/ case 0x6C: this.setMode(false); break;
|
|
/*n*/ case 0x6E: switch (this.par[0]) {
|
|
case 5: this.statusReport(); break;
|
|
case 6: this.cursorReport(); break;
|
|
default: break;
|
|
}
|
|
break;
|
|
/*q*/ case 0x71: // LED control not implemented
|
|
break;
|
|
/*r*/ case 0x72: 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;
|
|
/*b*/ case 0x62: 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;
|
|
/*s*/ case 0x73: this.saveCursor(); break;
|
|
/*u*/ case 0x75: this.restoreCursor(); break;
|
|
/*Z*/ case 0x5A: 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;
|
|
/*G*/ case 0x47:
|
|
/*8*/ case 0x38: this.utfEnabled = true; break;
|
|
default: break;
|
|
}
|
|
break;
|
|
case 6 /* ESfunckey */:
|
|
this.isEsc = 0 /* ESnormal */; break;
|
|
case 7 /* EShash */:
|
|
this.isEsc = 0 /* ESnormal */;
|
|
/*8*/ if (ch == 0x38) {
|
|
// Screen alignment test not implemented
|
|
}
|
|
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) {
|
|
/*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
|
|
/*A*/ case 0x42:
|
|
/*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
|
|
/*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
|
|
/*K*/ case 0x4B: 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;
|
|
default:
|
|
this.isEsc = 0 /* ESnormal */; break;
|
|
}
|
|
break;
|
|
}
|
|
return lineBuf;
|
|
};
|
|
|
|
VT100.prototype.renderString = function(s, showCursor) {
|
|
// 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.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.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.style);
|
|
}
|
|
this.lastCharacter = String.fromCharCode(ch);
|
|
lineBuf += this.lastCharacter;
|
|
if (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.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
|
|
];
|
|
|
|
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,
|
|
0x2588, 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, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
|
|
0x2591, 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
|
|
];
|
|
|