1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
69 // #define ESgetpars 3
70 // #define ESgotpars 4
71 // #define ESdeviceattr 5
72 // #define ESfunckey 6
79 // #define ESpercent 13
80 // #define ESignore 14
81 // #define ESnonstd 15
82 // #define ESpalette 16
83 // #define ESstatus 17
87 // #define ATTR_DEFAULT 0x00F0
88 // #define ATTR_REVERSE 0x0100
89 // #define ATTR_UNDERLINE 0x0200
90 // #define ATTR_DIM 0x0400
91 // #define ATTR_BRIGHT 0x0800
92 // #define ATTR_BLINK 0x1000
94 // #define MOUSE_DOWN 0
96 // #define MOUSE_CLICK 2
98 function VT100(container) {
99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
106 // Optionally allow username and passwords.
107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
110 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
115 '(?::[1-9][0-9]*)?' +
118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
125 // Hostnames must end with a well-known top-level domain or must be
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
146 '(?::[1-9][0-9]{0,4})?' +
149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
155 '[-_.+a-zA-Z0-9]+@' +
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
173 // Optional arguments
174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
176 this.getUserSettings();
177 this.initializeElements(container);
178 this.maxScrollbackLines = 500;
181 this.isQuestionMark = false;
184 this.savedAttr = [ ];
185 this.savedUseGMap = 0;
186 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187 this.CodePage437Map, this.DirectToFontMap ];
188 this.savedValid = [ ];
189 this.respondString = '';
190 this.statusString = '';
191 this.internalClipboard = undefined;
195 VT100.prototype.reset = function(clearHistory) {
196 this.isEsc = 0 /* ESnormal */;
197 this.needWrap = false;
198 this.autoWrapMode = true;
199 this.dispCtrl = false;
200 this.toggleMeta = false;
201 this.insertMode = false;
202 this.applKeyMode = false;
203 this.cursorKeyMode = false;
204 this.crLfMode = false;
205 this.offsetMode = false;
206 this.mouseReporting = false;
207 this.printing = false;
208 if (typeof this.printWin != 'undefined' &&
209 this.printWin && !this.printWin.closed) {
210 this.printWin.close();
212 this.printWin = null;
213 this.utfEnabled = this.utfPreferred;
216 this.color = 'ansi0 bgAnsi15';
218 this.attr = 0x00F0 /* ATTR_DEFAULT */;
220 this.GMap = [ this.Latin1Map,
221 this.VT100GraphicsMap,
223 this.DirectToFontMap];
224 this.translate = this.GMap[this.useGMap];
226 this.bottom = this.terminalHeight;
227 this.lastCharacter = ' ';
228 this.userTabStop = [ ];
231 for (var i = 0; i < 2; i++) {
232 while (this.console[i].firstChild) {
233 this.console[i].removeChild(this.console[i].firstChild);
238 this.enableAlternateScreen(false);
240 var wasCompressed = false;
241 var styles = [ 'transform',
245 for (var i = 0; i < styles.length; ++i) {
246 if (typeof this.console[0].style[styles[i]] != 'undefined') {
247 for (var j = 0; j < 1; ++j) {
248 wasCompressed |= this.console[j].style[styles[i]] != '';
249 this.console[j].style[styles[i]] = '';
251 this.cursor.style[styles[i]] = '';
252 this.space.style[styles[i]] = '';
253 if (styles[i] == 'filter') {
254 this.console[this.currentScreen].style.width = '';
266 this.isInverted = false;
267 this.refreshInvertedState();
268 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
269 this.color, this.style);
272 VT100.prototype.addListener = function(elem, event, listener) {
273 if (elem.addEventListener) {
274 elem.addEventListener(event, listener, false);
276 elem.attachEvent('on' + event, listener);
280 VT100.prototype.getUserSettings = function() {
281 // Compute hash signature to identify the entries in the userCSS menu.
282 // If the menu is unchanged from last time, default values can be
283 // looked up in a cookie associated with this page.
285 this.utfPreferred = true;
286 this.visualBell = typeof suppressAllAudio != 'undefined' &&
288 this.autoprint = true;
289 this.blinkingCursor = true;
290 if (this.visualBell) {
291 this.signature = Math.floor(16807*this.signature + 1) %
294 if (typeof userCSSList != 'undefined') {
295 for (var i = 0; i < userCSSList.length; ++i) {
296 var label = userCSSList[i][0];
297 for (var j = 0; j < label.length; ++j) {
298 this.signature = Math.floor(16807*this.signature+
299 label.charCodeAt(j)) %
302 if (userCSSList[i][1]) {
303 this.signature = Math.floor(16807*this.signature + 1) %
309 var key = 'shellInABox=' + this.signature + ':';
310 var settings = document.cookie.indexOf(key);
312 settings = document.cookie.substr(settings + key.length).
313 replace(/([0-1]*).*/, "$1");
314 if (settings.length == 3 + (typeof userCSSList == 'undefined' ?
315 0 : userCSSList.length)) {
316 this.utfPreferred = settings.charAt(0) != '0';
317 this.visualBell = settings.charAt(1) != '0';
318 this.autoprint = settings.charAt(2) != '0';
319 this.blinkingCursor = settings.charAt(3) != '0';
320 if (typeof userCSSList != 'undefined') {
321 for (var i = 0; i < userCSSList.length; ++i) {
322 userCSSList[i][2] = settings.charAt(i + 3) != '0';
327 this.utfEnabled = this.utfPreferred;
330 VT100.prototype.storeUserSettings = function() {
331 var settings = 'shellInABox=' + this.signature + ':' +
332 (this.utfEnabled ? '1' : '0') +
333 (this.visualBell ? '1' : '0') +
334 (this.autoprint ? '1' : '0') +
335 (this.blinkingCursor ? '1' : '0');
336 if (typeof userCSSList != 'undefined') {
337 for (var i = 0; i < userCSSList.length; ++i) {
338 settings += userCSSList[i][2] ? '1' : '0';
342 d.setDate(d.getDate() + 3653);
343 document.cookie = settings + ';expires=' + d.toGMTString();
346 VT100.prototype.initializeUserCSSStyles = function() {
347 this.usercssActions = [];
348 if (typeof userCSSList != 'undefined') {
351 var wasSingleSel = 1;
352 var beginOfGroup = 0;
353 for (var i = 0; i <= userCSSList.length; ++i) {
354 if (i < userCSSList.length) {
355 var label = userCSSList[i][0];
356 var newGroup = userCSSList[i][1];
357 var enabled = userCSSList[i][2];
359 // Add user style sheet to document
360 var style = document.createElement('link');
361 var id = document.createAttribute('id');
362 id.nodeValue = 'usercss-' + i;
363 style.setAttributeNode(id);
364 var rel = document.createAttribute('rel');
365 rel.nodeValue = 'stylesheet';
366 style.setAttributeNode(rel);
367 var href = document.createAttribute('href');
368 href.nodeValue = 'usercss-' + i + '.css';
369 style.setAttributeNode(href);
370 var type = document.createAttribute('type');
371 type.nodeValue = 'text/css';
372 style.setAttributeNode(type);
373 document.getElementsByTagName('head')[0].appendChild(style);
374 style.disabled = !enabled;
378 if (newGroup || i == userCSSList.length) {
379 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
380 // The last group had multiple entries that are mutually exclusive;
381 // or the previous to last group did. In either case, we need to
382 // append a "<hr />" before we can add the last group to the menu.
385 wasSingleSel = i - beginOfGroup < 1;
389 for (var j = beginOfGroup; j < i; ++j) {
390 this.usercssActions[this.usercssActions.length] =
391 function(vt100, current, begin, count) {
393 // Deselect all other entries in the group, then either select
394 // (for multiple entries in group) or toggle (for on/off entry)
395 // the current entry.
397 var entry = vt100.getChildById(vt100.menu,
401 for (var c = count; c > 0; ++j) {
402 if (entry.tagName == 'LI') {
405 var label = vt100.usercss.childNodes[j];
407 // Restore label to just the text content
408 if (typeof label.textContent == 'undefined') {
409 var s = label.innerText;
410 label.innerHTML = '';
411 label.appendChild(document.createTextNode(s));
413 label.textContent= label.textContent;
416 // User style sheets are number sequentially
417 var sheet = document.getElementById(
421 sheet.disabled = !sheet.disabled;
423 sheet.disabled = false;
425 if (!sheet.disabled) {
426 label.innerHTML= '<img src="enabled.gif" />' +
430 sheet.disabled = true;
432 userCSSList[i][2] = !sheet.disabled;
435 entry = entry.nextSibling;
438 // If the font size changed, adjust cursor and line dimensions
439 this.cursor.style.cssText= '';
440 this.cursorWidth = this.cursor.clientWidth;
441 this.cursorHeight = this.lineheight.clientHeight;
442 for (i = 0; i < this.console.length; ++i) {
443 for (var line = this.console[i].firstChild; line;
444 line = line.nextSibling) {
445 line.style.height = this.cursorHeight + 'px';
450 }(this, j, beginOfGroup, i - beginOfGroup);
453 if (i == userCSSList.length) {
459 // Collect all entries in a group, before attaching them to the menu.
460 // This is necessary as we don't know whether this is a group of
461 // mutually exclusive options (which should be separated by "<hr />" on
462 // both ends), or whether this is a on/off toggle, which can be grouped
463 // together with other on/off options.
465 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
469 this.usercss.innerHTML = menu;
473 VT100.prototype.initializeElements = function(container) {
474 // If the necessary objects have not already been defined in the HTML
475 // page, create them now.
477 this.container = container;
478 } else if (!(this.container = document.getElementById('vt100'))) {
479 this.container = document.createElement('div');
480 this.container.id = 'vt100';
481 document.body.appendChild(this.container);
484 if (!this.getChildById(this.container, 'reconnect') ||
485 !this.getChildById(this.container, 'menu') ||
486 !this.getChildById(this.container, 'scrollable') ||
487 !this.getChildById(this.container, 'console') ||
488 !this.getChildById(this.container, 'alt_console') ||
489 !this.getChildById(this.container, 'ieprobe') ||
490 !this.getChildById(this.container, 'padding') ||
491 !this.getChildById(this.container, 'cursor') ||
492 !this.getChildById(this.container, 'lineheight') ||
493 !this.getChildById(this.container, 'usercss') ||
494 !this.getChildById(this.container, 'space') ||
495 !this.getChildById(this.container, 'input') ||
496 !this.getChildById(this.container, 'cliphelper')) {
497 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
498 // we might get a pointless warning that a suitable plugin is not yet
499 // installed. If in doubt, we'd rather just stay silent.
502 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
504 embed = typeof suppressAllAudio != 'undefined' &&
505 suppressAllAudio ? "" :
506 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
509 'autostart="false" ' +
511 'enablejavascript="true" ' +
512 'type="audio/x-wav" ' +
515 'style="position:absolute;left:-1000px;top:-1000px" />';
520 this.container.innerHTML =
521 '<div id="reconnect" style="visibility: hidden">' +
522 '<input type="button" value="Connect" ' +
523 'onsubmit="return false" />' +
525 '<div id="cursize" style="visibility: hidden">' +
527 '<div id="menu"></div>' +
528 '<div id="scrollable">' +
529 '<pre id="lineheight"> </pre>' +
530 '<pre id="console">' +
532 '<div id="ieprobe"><span> </span></div>' +
534 '<pre id="alt_console" style="display: none"></pre>' +
535 '<div id="padding"></div>' +
536 '<pre id="cursor"> </pre>' +
538 '<div class="hidden">' +
539 '<div id="usercss"></div>' +
540 '<pre><div><span id="space"></span></div></pre>' +
541 '<input type="textfield" id="input" />' +
542 '<input type="textfield" id="cliphelper" />' +
543 (typeof suppressAllAudio != 'undefined' &&
544 suppressAllAudio ? "" :
545 embed + '<bgsound id="beep_bgsound" loop=1 />') +
549 // Find the object used for playing the "beep" sound, if any.
550 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
551 this.beeper = undefined;
553 this.beeper = this.getChildById(this.container,
555 if (!this.beeper || !this.beeper.Play) {
556 this.beeper = this.getChildById(this.container,
558 if (!this.beeper || typeof this.beeper.src == 'undefined') {
559 this.beeper = undefined;
564 // Initialize the variables for finding the text console and the
566 this.reconnectBtn = this.getChildById(this.container,'reconnect');
567 this.curSizeBox = this.getChildById(this.container, 'cursize');
568 this.menu = this.getChildById(this.container, 'menu');
569 this.scrollable = this.getChildById(this.container,
571 this.lineheight = this.getChildById(this.container,
574 [ this.getChildById(this.container, 'console'),
575 this.getChildById(this.container, 'alt_console') ];
576 var ieProbe = this.getChildById(this.container, 'ieprobe');
577 this.padding = this.getChildById(this.container, 'padding');
578 this.cursor = this.getChildById(this.container, 'cursor');
579 this.usercss = this.getChildById(this.container, 'usercss');
580 this.space = this.getChildById(this.container, 'space');
581 this.input = this.getChildById(this.container, 'input');
582 this.cliphelper = this.getChildById(this.container,
585 // Add any user selectable style sheets to the menu
586 this.initializeUserCSSStyles();
588 // Remember the dimensions of a standard character glyph. We would
589 // expect that we could just check cursor.clientWidth/Height at any time,
590 // but it turns out that browsers sometimes invalidate these values
591 // (e.g. while displaying a print preview screen).
592 this.cursorWidth = this.cursor.clientWidth;
593 this.cursorHeight = this.lineheight.clientHeight;
595 // IE has a slightly different boxing model, that we need to compensate for
596 this.isIE = ieProbe.offsetTop > 1;
598 this.console.innerHTML = '';
600 // Determine if the terminal window is positioned at the beginning of the
601 // page, or if it is embedded somewhere else in the page. For full-screen
602 // terminals, automatically resize whenever the browser window changes.
603 var marginTop = parseInt(this.getCurrentComputedStyle(
604 document.body, 'marginTop'));
605 var marginLeft = parseInt(this.getCurrentComputedStyle(
606 document.body, 'marginLeft'));
607 var marginRight = parseInt(this.getCurrentComputedStyle(
608 document.body, 'marginRight'));
609 var x = this.container.offsetLeft;
610 var y = this.container.offsetTop;
611 for (var parent = this.container; parent = parent.offsetParent; ) {
612 x += parent.offsetLeft;
613 y += parent.offsetTop;
615 this.isEmbedded = marginTop != y ||
617 (window.innerWidth ||
618 document.documentElement.clientWidth ||
619 document.body.clientWidth) -
620 marginRight != x + this.container.offsetWidth;
621 if (!this.isEmbedded) {
622 // Some browsers generate resize events when the terminal is first
623 // shown. Disable showing the size indicator until a little bit after
624 // the terminal has been rendered the first time.
625 this.indicateSize = false;
626 setTimeout(function(vt100) {
628 vt100.indicateSize = true;
631 this.addListener(window, 'resize',
634 vt100.hideContextMenu();
636 vt100.showCurrentSize();
640 // Hide extra scrollbars attached to window
641 document.body.style.margin = '0px';
642 try { document.body.style.overflow ='hidden'; } catch (e) { }
643 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
647 this.hideContextMenu();
649 // Add listener to reconnect button
650 this.addListener(this.reconnectBtn.firstChild, 'click',
653 var rc = vt100.reconnect();
659 // Add input listeners
660 this.addListener(this.input, 'blur',
662 return function() { vt100.blurCursor(); } }(this));
663 this.addListener(this.input, 'focus',
665 return function() { vt100.focusCursor(); } }(this));
666 this.addListener(this.input, 'keydown',
669 if (!e) e = window.event;
670 return vt100.keyDown(e); } }(this));
671 this.addListener(this.input, 'keypress',
674 if (!e) e = window.event;
675 return vt100.keyPressed(e); } }(this));
676 this.addListener(this.input, 'keyup',
679 if (!e) e = window.event;
680 return vt100.keyUp(e); } }(this));
682 // Attach listeners that move the focus to the <input> field. This way we
683 // can make sure that we can receive keyboard input.
684 var mouseEvent = function(vt100, type) {
686 if (!e) e = window.event;
687 return vt100.mouseEvent(e, type);
690 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
691 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
692 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
694 // Initialize the blank terminal window.
695 this.currentScreen = 0;
698 this.numScrollbackLines = 0;
700 this.bottom = 0x7FFFFFFF;
707 VT100.prototype.getChildById = function(parent, id) {
708 var nodeList = parent.all || parent.getElementsByTagName('*');
709 if (typeof nodeList.namedItem == 'undefined') {
710 for (var i = 0; i < nodeList.length; i++) {
711 if (nodeList[i].id == id) {
717 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
718 return elem ? elem[0] || elem : null;
722 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
723 if (typeof elem.currentStyle != 'undefined') {
724 return elem.currentStyle[style];
726 return document.defaultView.getComputedStyle(elem, null)[style];
730 VT100.prototype.reconnect = function() {
734 VT100.prototype.showReconnect = function(state) {
736 this.reconnectBtn.style.visibility = '';
738 this.reconnectBtn.style.visibility = 'hidden';
742 VT100.prototype.repairElements = function(console) {
743 for (var line = console.firstChild; line; line = line.nextSibling) {
744 if (!line.clientHeight) {
745 var newLine = document.createElement(line.tagName);
746 newLine.style.cssText = line.style.cssText;
747 newLine.className = line.className;
748 if (line.tagName == 'DIV') {
749 for (var span = line.firstChild; span; span = span.nextSibling) {
750 var newSpan = document.createElement(span.tagName);
751 newSpan.style.cssText = span.style.cssText;
752 newSpan.style.className = span.style.className;
753 this.setTextContent(newSpan, this.getTextContent(span));
754 newLine.appendChild(newSpan);
757 this.setTextContent(newLine, this.getTextContent(line));
759 line.parentNode.replaceChild(newLine, line);
765 VT100.prototype.resized = function(w, h) {
768 VT100.prototype.resizer = function() {
769 // The cursor can get corrupted if the print-preview is displayed in Firefox.
770 // Recreating it, will repair it.
771 var newCursor = document.createElement('pre');
772 this.setTextContent(newCursor, ' ');
773 newCursor.id = 'cursor';
774 newCursor.style.cssText = this.cursor.style.cssText;
775 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
776 if (!newCursor.clientHeight) {
777 // Things are broken right now. This is probably because we are
778 // displaying the print-preview. Just don't change any of our settings
779 // until the print dialog is closed again.
780 newCursor.parentNode.removeChild(newCursor);
783 // Swap the old broken cursor for the newly created one.
784 this.cursor.parentNode.removeChild(this.cursor);
785 this.cursor = newCursor;
788 // Really horrible things happen if the contents of the terminal changes
789 // while the print-preview is showing. We get HTML elements that show up
790 // in the DOM, but that do not take up any space. Find these elements and
792 this.repairElements(this.console[0]);
793 this.repairElements(this.console[1]);
795 // Lock the cursor size to the size of a normal character. This helps with
796 // characters that are taller/shorter than normal. Unfortunately, we will
797 // still get confused if somebody enters a character that is wider/narrower
798 // than normal. This can happen if the browser tries to substitute a
799 // characters from a different font.
800 this.cursor.style.width = this.cursorWidth + 'px';
801 this.cursor.style.height = this.cursorHeight + 'px';
803 // Adjust height for one pixel padding of the #vt100 element.
804 // The latter is necessary to properly display the inactive cursor.
805 var console = this.console[this.currentScreen];
806 var height = (this.isEmbedded ? this.container.clientHeight
807 : (window.innerHeight ||
808 document.documentElement.clientHeight ||
809 document.body.clientHeight))-1;
810 var partial = height % this.cursorHeight;
811 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
812 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
813 var oldTerminalHeight = this.terminalHeight;
817 // Clip the cursor to the visible screen.
818 var cx = this.cursorX;
819 var cy = this.cursorY + this.numScrollbackLines;
821 // The alternate screen never keeps a scroll back buffer.
822 this.updateNumScrollbackLines();
823 while (this.currentScreen && this.numScrollbackLines > 0) {
824 console.removeChild(console.firstChild);
825 this.numScrollbackLines--;
827 cy -= this.numScrollbackLines;
830 } else if (cx > this.terminalWidth) {
831 cx = this.terminalWidth - 1;
838 } else if (cy > this.terminalHeight) {
839 cy = this.terminalHeight - 1;
845 // Clip the scroll region to the visible screen.
846 if (this.bottom > this.terminalHeight ||
847 this.bottom == oldTerminalHeight) {
848 this.bottom = this.terminalHeight;
850 if (this.top >= this.bottom) {
851 this.top = this.bottom-1;
857 // Truncate lines, if necessary. Explicitly reposition cursor (this is
858 // particularly important after changing the screen number), and reset
859 // the scroll region to the default.
860 this.truncateLines(this.terminalWidth);
861 this.putString(cx, cy, '', undefined);
862 this.scrollable.scrollTop = this.numScrollbackLines *
863 this.cursorHeight + 1;
865 // Update classNames for lines in the scrollback buffer
866 var line = console.firstChild;
867 for (var i = 0; i < this.numScrollbackLines; i++) {
868 line.className = 'scrollback';
869 line = line.nextSibling;
873 line = line.nextSibling;
876 // Reposition the reconnect button
877 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
879 this.reconnectBtn.clientWidth)/2 + 'px';
880 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
881 this.reconnectBtn.clientHeight)/2 + 'px';
883 // Send notification that the window size has been changed
884 this.resized(this.terminalWidth, this.terminalHeight);
887 VT100.prototype.showCurrentSize = function() {
888 if (!this.indicateSize) {
891 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
893 this.curSizeBox.style.left =
894 (this.terminalWidth*this.cursorWidth/
896 this.curSizeBox.clientWidth)/2 + 'px';
897 this.curSizeBox.style.top =
898 (this.terminalHeight*this.cursorHeight -
899 this.curSizeBox.clientHeight)/2 + 'px';
900 this.curSizeBox.style.visibility = '';
901 if (this.curSizeTimeout) {
902 clearTimeout(this.curSizeTimeout);
905 // Only show the terminal size for a short amount of time after resizing.
906 // Then hide this information, again. Some browsers generate resize events
907 // throughout the entire resize operation. This is nice, and we will show
908 // the terminal size while the user is dragging the window borders.
909 // Other browsers only generate a single event when the user releases the
910 // mouse. In those cases, we can only show the terminal size once at the
911 // end of the resize operation.
912 this.curSizeTimeout = setTimeout(function(vt100) {
914 vt100.curSizeTimeout = null;
915 vt100.curSizeBox.style.visibility = 'hidden';
920 VT100.prototype.selection = function() {
922 return '' + (window.getSelection && window.getSelection() ||
923 document.selection && document.selection.type == 'Text' &&
924 document.selection.createRange().text || '');
930 VT100.prototype.cancelEvent = function(event) {
932 // For non-IE browsers
933 event.stopPropagation();
934 event.preventDefault();
939 event.cancelBubble = true;
940 event.returnValue = false;
948 VT100.prototype.mouseEvent = function(event, type) {
949 // If any text is currently selected, do not move the focus as that would
950 // invalidate the selection.
951 var selection = this.selection();
952 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
956 // Compute mouse position in characters.
957 var offsetX = this.container.offsetLeft;
958 var offsetY = this.container.offsetTop;
959 for (var e = this.container; e = e.offsetParent; ) {
960 offsetX += e.offsetLeft;
961 offsetY += e.offsetTop;
963 var x = (event.clientX - offsetX) / this.cursorWidth;
964 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
965 this.cursorHeight - this.numScrollbackLines;
967 if (x >= this.terminalWidth) {
968 x = this.terminalWidth - 1;
975 if (y >= this.terminalHeight) {
976 y = this.terminalHeight - 1;
984 // Compute button number and modifier keys.
985 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
986 typeof event.pageX != 'undefined' ? event.button :
987 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
988 if (button != undefined) {
989 if (event.shiftKey) {
992 if (event.altKey || event.metaKey) {
1000 // Report mouse events if they happen inside of the current screen and
1001 // with the SHIFT key unpressed. Both of these restrictions do not apply
1002 // for button releases, as we always want to report those.
1003 if (this.mouseReporting && !selection.length &&
1004 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1005 if (inside || type != 0 /* MOUSE_DOWN */) {
1006 if (button != undefined) {
1007 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1008 String.fromCharCode(x + 33) +
1009 String.fromCharCode(y + 33);
1010 if (type != 2 /* MOUSE_CLICK */) {
1011 this.keysPressed(report);
1014 // If we reported the event, stop propagating it (not sure, if this
1015 // actually works on most browsers; blocking the global "oncontextmenu"
1016 // even is still necessary).
1017 return this.cancelEvent(event);
1022 // Bring up context menu.
1023 if (button == 2 && !event.shiftKey) {
1024 if (type == 0 /* MOUSE_DOWN */) {
1025 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
1027 return this.cancelEvent(event);
1030 if (this.mouseReporting) {
1032 event.shiftKey = false;
1040 VT100.prototype.replaceChar = function(s, ch, repl) {
1041 for (var i = -1;;) {
1042 i = s.indexOf(ch, i + 1);
1046 s = s.substr(0, i) + repl + s.substr(i + 1);
1051 VT100.prototype.htmlEscape = function(s) {
1052 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1053 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1056 VT100.prototype.getTextContent = function(elem) {
1057 return elem.textContent ||
1058 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1061 VT100.prototype.setTextContent = function(elem, s) {
1062 // Check if we find any URLs in the text. If so, automatically convert them
1064 if (this.urlRE && this.urlRE.test(s)) {
1068 if (RegExp.leftContext != null) {
1069 inner += this.htmlEscape(RegExp.leftContext);
1070 consumed += RegExp.leftContext.length;
1072 var url = this.htmlEscape(RegExp.lastMatch);
1075 // If no protocol was specified, try to guess a reasonable one.
1076 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1077 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1078 var slash = url.indexOf('/');
1079 var at = url.indexOf('@');
1080 var question = url.indexOf('?');
1082 (at < question || question < 0) &&
1083 (slash < 0 || (question > 0 && slash > question))) {
1084 fullUrl = 'mailto:' + url;
1086 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1091 inner += '<a target="vt100Link" href="' + fullUrl +
1092 '">' + url + '</a>';
1093 consumed += RegExp.lastMatch.length;
1094 s = s.substr(consumed);
1095 if (!this.urlRE.test(s)) {
1096 if (RegExp.rightContext != null) {
1097 inner += this.htmlEscape(RegExp.rightContext);
1102 elem.innerHTML = inner;
1106 // Updating the content of an element is an expensive operation. It actually
1107 // pays off to first check whether the element is still unchanged.
1108 if (typeof elem.textContent == 'undefined') {
1109 if (elem.innerText != s) {
1113 // Very old versions of IE do not allow setting innerText. Instead,
1114 // remove all children, by setting innerHTML and then set the text
1115 // using DOM methods.
1116 elem.innerHTML = '';
1117 elem.appendChild(document.createTextNode(
1118 this.replaceChar(s, ' ', '\u00A0')));
1122 if (elem.textContent != s) {
1123 elem.textContent = s;
1128 VT100.prototype.insertBlankLine = function(y, color, style) {
1129 // Insert a blank line a position y. This method ignores the scrollback
1130 // buffer. The caller has to add the length of the scrollback buffer to
1131 // the position, if necessary.
1132 // If the position is larger than the number of current lines, this
1133 // method just adds a new line right after the last existing one. It does
1134 // not add any missing lines in between. It is the caller's responsibility
1137 color = 'ansi0 bgAnsi15';
1143 if (color != 'ansi0 bgAnsi15' && !style) {
1144 line = document.createElement('pre');
1145 this.setTextContent(line, '\n');
1147 line = document.createElement('div');
1148 var span = document.createElement('span');
1149 span.style.cssText = style;
1150 span.style.className = color;
1151 this.setTextContent(span, this.spaces(this.terminalWidth));
1152 line.appendChild(span);
1154 line.style.height = this.cursorHeight + 'px';
1155 var console = this.console[this.currentScreen];
1156 if (console.childNodes.length > y) {
1157 console.insertBefore(line, console.childNodes[y]);
1159 console.appendChild(line);
1163 VT100.prototype.updateWidth = function() {
1164 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1165 this.cursorWidth*this.scale);
1166 return this.terminalWidth;
1169 VT100.prototype.updateHeight = function() {
1170 // We want to be able to display either a terminal window that fills the
1171 // entire browser window, or a terminal window that is contained in a
1172 // <div> which is embededded somewhere in the web page.
1173 if (this.isEmbedded) {
1174 // Embedded terminal. Use size of the containing <div> (id="vt100").
1175 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1178 // Use the full browser window.
1179 this.terminalHeight = Math.floor(((window.innerHeight ||
1180 document.documentElement.clientHeight ||
1181 document.body.clientHeight)-1)/
1184 return this.terminalHeight;
1187 VT100.prototype.updateNumScrollbackLines = function() {
1188 var scrollback = Math.floor(
1189 this.console[this.currentScreen].offsetHeight /
1190 this.cursorHeight) -
1191 this.terminalHeight;
1192 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1193 return this.numScrollbackLines;
1196 VT100.prototype.truncateLines = function(width) {
1200 for (var line = this.console[this.currentScreen].firstChild; line;
1201 line = line.nextSibling) {
1202 if (line.tagName == 'DIV') {
1205 // Traverse current line and truncate it once we saw "width" characters
1206 for (var span = line.firstChild; span;
1207 span = span.nextSibling) {
1208 var s = this.getTextContent(span);
1210 if (x + l > width) {
1211 this.setTextContent(span, s.substr(0, width - x));
1212 while (span.nextSibling) {
1213 line.removeChild(line.lastChild);
1219 // Prune white space from the end of the current line
1220 var span = line.lastChild;
1222 span.className == 'ansi0 bgAnsi15' &&
1223 !span.style.cssText.length) {
1224 // Scan backwards looking for first non-space character
1225 var s = this.getTextContent(span);
1226 for (var i = s.length; i--; ) {
1227 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1228 if (i+1 != s.length) {
1229 this.setTextContent(s.substr(0, i+1));
1237 span = span.previousSibling;
1239 // Remove blank <span>'s from end of line
1240 line.removeChild(sibling);
1242 // Remove entire line (i.e. <div>), if empty
1243 var blank = document.createElement('pre');
1244 blank.style.height = this.cursorHeight + 'px';
1245 this.setTextContent(blank, '\n');
1246 line.parentNode.replaceChild(blank, line);
1254 VT100.prototype.putString = function(x, y, text, color, style) {
1256 color = 'ansi0 bgAnsi15';
1261 var yIdx = y + this.numScrollbackLines;
1267 var console = this.console[this.currentScreen];
1268 if (!text.length && (yIdx >= console.childNodes.length ||
1269 console.childNodes[yIdx].tagName != 'DIV')) {
1270 // Positioning cursor to a blank location
1273 // Create missing blank lines at end of page
1274 while (console.childNodes.length <= yIdx) {
1275 // In order to simplify lookups, we want to make sure that each line
1276 // is represented by exactly one element (and possibly a whole bunch of
1278 // For non-blank lines, we can create a <div> containing one or more
1279 // <span>s. For blank lines, this fails as browsers tend to optimize them
1280 // away. But fortunately, a <pre> tag containing a newline character
1281 // appears to work for all browsers (a would also work, but then
1282 // copying from the browser window would insert superfluous spaces into
1284 this.insertBlankLine(yIdx);
1286 line = console.childNodes[yIdx];
1288 // If necessary, promote blank '\n' line to a <div> tag
1289 if (line.tagName != 'DIV') {
1290 var div = document.createElement('div');
1291 div.style.height = this.cursorHeight + 'px';
1292 div.innerHTML = '<span></span>';
1293 console.replaceChild(div, line);
1297 // Scan through list of <span>'s until we find the one where our text
1299 span = line.firstChild;
1301 while (span.nextSibling && xPos < x) {
1302 len = this.getTextContent(span).length;
1303 if (xPos + len > x) {
1307 span = span.nextSibling;
1311 // If current <span> is not long enough, pad with spaces or add new
1313 s = this.getTextContent(span);
1314 var oldColor = span.className;
1315 var oldStyle = span.style.cssText;
1316 if (xPos + s.length < x) {
1317 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1318 span = document.createElement('span');
1319 line.appendChild(span);
1320 span.className = 'ansi0 bgAnsi15';
1321 span.style.cssText = '';
1322 oldColor = 'ansi0 bgAnsi15';
1329 } while (xPos + s.length < x);
1332 // If styles do not match, create a new <span>
1333 var del = text.length - s.length + x - xPos;
1334 if (oldColor != color ||
1335 (oldStyle != style && (oldStyle || style))) {
1337 // Replacing text at beginning of existing <span>
1338 if (text.length >= s.length) {
1339 // New text is equal or longer than existing text
1342 // Insert new <span> before the current one, then remove leading
1343 // part of existing <span>, adjust style of new <span>, and finally
1345 sibling = document.createElement('span');
1346 line.insertBefore(sibling, span);
1347 this.setTextContent(span, s.substr(text.length));
1352 // Replacing text some way into the existing <span>
1353 var remainder = s.substr(x + text.length - xPos);
1354 this.setTextContent(span, s.substr(0, x - xPos));
1356 sibling = document.createElement('span');
1357 if (span.nextSibling) {
1358 line.insertBefore(sibling, span.nextSibling);
1360 if (remainder.length) {
1361 sibling = document.createElement('span');
1362 sibling.className = oldColor;
1363 sibling.style.cssText = oldStyle;
1364 this.setTextContent(sibling, remainder);
1365 line.insertBefore(sibling, span.nextSibling);
1368 line.appendChild(sibling);
1370 if (remainder.length) {
1371 sibling = document.createElement('span');
1372 sibling.className = oldColor;
1373 sibling.style.cssText = oldStyle;
1374 this.setTextContent(sibling, remainder);
1375 line.appendChild(sibling);
1380 span.className = color;
1381 span.style.cssText = style;
1383 // Overwrite (partial) <span> with new text
1384 s = s.substr(0, x - xPos) +
1386 s.substr(x + text.length - xPos);
1388 this.setTextContent(span, s);
1391 // Delete all subsequent <span>'s that have just been overwritten
1392 sibling = span.nextSibling;
1393 while (del > 0 && sibling) {
1394 s = this.getTextContent(sibling);
1397 line.removeChild(sibling);
1399 sibling = span.nextSibling;
1401 this.setTextContent(sibling, s.substr(del));
1406 // Merge <span> with next sibling, if styles are identical
1407 if (sibling && span.className == sibling.className &&
1408 span.style.cssText == sibling.style.cssText) {
1409 this.setTextContent(span,
1410 this.getTextContent(span) +
1411 this.getTextContent(sibling));
1412 line.removeChild(sibling);
1418 this.cursorX = x + text.length;
1419 if (this.cursorX >= this.terminalWidth) {
1420 this.cursorX = this.terminalWidth - 1;
1421 if (this.cursorX < 0) {
1427 if (!this.cursor.style.visibility) {
1428 var idx = this.cursorX - xPos;
1430 // If we are in a non-empty line, take the cursor Y position from the
1431 // other elements in this line. If dealing with broken, non-proportional
1432 // fonts, this is likely to yield better results.
1433 pixelY = span.offsetTop +
1434 span.offsetParent.offsetTop;
1435 s = this.getTextContent(span);
1436 var nxtIdx = idx - s.length;
1438 this.setTextContent(this.cursor, s.charAt(idx));
1439 pixelX = span.offsetLeft +
1440 idx*span.offsetWidth / s.length;
1443 pixelX = span.offsetLeft + span.offsetWidth;
1445 if (span.nextSibling) {
1446 s = this.getTextContent(span.nextSibling);
1447 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1449 pixelX = span.nextSibling.offsetLeft +
1450 nxtIdx*span.offsetWidth / s.length;
1453 this.setTextContent(this.cursor, ' ');
1457 this.setTextContent(this.cursor, ' ');
1461 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
1464 this.setTextContent(this.space, this.spaces(this.cursorX));
1465 this.cursor.style.left = (this.space.offsetWidth +
1466 console.offsetLeft)/this.scale + 'px';
1468 this.cursorY = yIdx - this.numScrollbackLines;
1470 this.cursor.style.top = pixelY + 'px';
1472 this.cursor.style.top = yIdx*this.cursorHeight +
1473 console.offsetTop + 'px';
1477 // Merge <span> with previous sibling, if styles are identical
1478 if ((sibling = span.previousSibling) &&
1479 span.className == sibling.className &&
1480 span.style.cssText == sibling.style.cssText) {
1481 this.setTextContent(span,
1482 this.getTextContent(sibling) +
1483 this.getTextContent(span));
1484 line.removeChild(sibling);
1487 // Prune white space from the end of the current line
1488 span = line.lastChild;
1490 span.className == 'ansi0 bgAnsi15' &&
1491 !span.style.cssText.length) {
1492 // Scan backwards looking for first non-space character
1493 s = this.getTextContent(span);
1494 for (var i = s.length; i--; ) {
1495 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1496 if (i+1 != s.length) {
1497 this.setTextContent(s.substr(0, i+1));
1505 span = span.previousSibling;
1507 // Remove blank <span>'s from end of line
1508 line.removeChild(sibling);
1510 // Remove entire line (i.e. <div>), if empty
1511 var blank = document.createElement('pre');
1512 blank.style.height = this.cursorHeight + 'px';
1513 this.setTextContent(blank, '\n');
1514 line.parentNode.replaceChild(blank, line);
1521 VT100.prototype.gotoXY = function(x, y) {
1522 if (x >= this.terminalWidth) {
1523 x = this.terminalWidth - 1;
1529 if (this.offsetMode) {
1534 maxY = this.terminalHeight;
1542 this.putString(x, y, '', undefined);
1543 this.needWrap = false;
1546 VT100.prototype.gotoXaY = function(x, y) {
1547 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1550 VT100.prototype.refreshInvertedState = function() {
1551 if (this.isInverted) {
1552 this.scrollable.className += ' inverted';
1554 this.scrollable.className = this.scrollable.className.
1555 replace(/ *inverted/, '');
1559 VT100.prototype.enableAlternateScreen = function(state) {
1560 // Don't do anything, if we are already on the desired screen
1561 if ((state ? 1 : 0) == this.currentScreen) {
1562 // Calling the resizer is not actually necessary. But it is a good way
1563 // of resetting state that might have gotten corrupted.
1568 // We save the full state of the normal screen, when we switch away from it.
1569 // But for the alternate screen, no saving is necessary. We always reset
1570 // it when we switch to it.
1575 // Display new screen, and initialize state (the resizer does that for us).
1576 this.currentScreen = state ? 1 : 0;
1577 this.console[1-this.currentScreen].style.display = 'none';
1578 this.console[this.currentScreen].style.display = '';
1580 // Select appropriate character pitch.
1581 var styles = [ 'transform',
1585 for (var i = 0; i < styles.length; ++i) {
1586 if (typeof this.console[0].style[styles[i]] != 'undefined') {
1588 // Upon enabling the alternate screen, we switch to 80 column mode. But
1589 // upon returning to the regular screen, we restore the mode that was
1590 // in effect previously.
1591 this.console[1].style[styles[i]] = '';
1594 this.console[this.currentScreen].style[styles[i]];
1595 this.cursor.style[styles[i]] = style;
1596 this.space.style[styles[i]] = style;
1597 this.scale = style == '' ? 1.0:1.65;
1598 if (styles[i] == 'filter') {
1599 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
1606 // If we switched to the alternate screen, reset it completely. Otherwise,
1607 // restore the saved state.
1610 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1612 this.restoreCursor();
1616 VT100.prototype.hideCursor = function() {
1617 var hidden = this.cursor.style.visibility == 'hidden';
1619 this.cursor.style.visibility = 'hidden';
1625 VT100.prototype.showCursor = function(x, y) {
1626 if (this.cursor.style.visibility) {
1627 this.cursor.style.visibility = '';
1628 this.putString(x == undefined ? this.cursorX : x,
1629 y == undefined ? this.cursorY : y,
1636 VT100.prototype.scrollBack = function() {
1637 var i = this.scrollable.scrollTop -
1638 this.scrollable.clientHeight;
1639 this.scrollable.scrollTop = i < 0 ? 0 : i;
1642 VT100.prototype.scrollFore = function() {
1643 var i = this.scrollable.scrollTop +
1644 this.scrollable.clientHeight;
1645 this.scrollable.scrollTop = i > this.numScrollbackLines *
1646 this.cursorHeight + 1
1647 ? this.numScrollbackLines *
1648 this.cursorHeight + 1
1652 VT100.prototype.spaces = function(i) {
1660 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
1665 if (w > this.terminalWidth) {
1666 w = this.terminalWidth;
1668 if ((w -= x) <= 0) {
1675 if (h > this.terminalHeight) {
1676 h = this.terminalHeight;
1678 if ((h -= y) <= 0) {
1682 // Special case the situation where we clear the entire screen, and we do
1683 // not have a scrollback buffer. In that case, we should just remove all
1685 if (!this.numScrollbackLines &&
1686 w == this.terminalWidth && h == this.terminalHeight &&
1687 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
1688 var console = this.console[this.currentScreen];
1689 while (console.lastChild) {
1690 console.removeChild(console.lastChild);
1692 this.putString(this.cursorX, this.cursorY, '', undefined);
1694 var hidden = this.hideCursor();
1695 var cx = this.cursorX;
1696 var cy = this.cursorY;
1697 var s = this.spaces(w);
1698 for (var i = y+h; i-- > y; ) {
1699 this.putString(x, i, s, color, style);
1701 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1705 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1707 var className = [ ];
1709 var console = this.console[this.currentScreen];
1710 if (sY >= console.childNodes.length) {
1711 text[0] = this.spaces(w);
1712 className[0] = undefined;
1713 style[0] = undefined;
1715 var line = console.childNodes[sY];
1716 if (line.tagName != 'DIV' || !line.childNodes.length) {
1717 text[0] = this.spaces(w);
1718 className[0] = undefined;
1719 style[0] = undefined;
1722 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1723 var s = this.getTextContent(span);
1726 var o = sX > x ? sX - x : 0;
1727 text[text.length] = s.substr(o, w);
1728 className[className.length] = span.className;
1729 style[style.length] = span.style.cssText;
1735 text[text.length] = this.spaces(w);
1736 className[className.length] = undefined;
1737 style[style.length] = undefined;
1741 var hidden = this.hideCursor();
1742 var cx = this.cursorX;
1743 var cy = this.cursorY;
1744 for (var i = 0; i < text.length; i++) {
1747 color = className[i];
1749 color = 'ansi0 bgAnsi15';
1751 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
1752 dX += text[i].length;
1754 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1757 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
1759 var left = incX < 0 ? -incX : 0;
1760 var right = incX > 0 ? incX : 0;
1761 var up = incY < 0 ? -incY : 0;
1762 var down = incY > 0 ? incY : 0;
1764 // Clip region against terminal size
1765 var dontScroll = null;
1770 if (w > this.terminalWidth - right) {
1771 w = this.terminalWidth - right;
1773 if ((w -= x) <= 0) {
1780 if (h > this.terminalHeight - down) {
1781 h = this.terminalHeight - down;
1787 if (style && style.indexOf('underline')) {
1788 // Different terminal emulators disagree on the attributes that
1789 // are used for scrolling. The consensus seems to be, never to
1790 // fill with underlined spaces. N.B. this is different from the
1791 // cases when the user blanks a region. User-initiated blanking
1792 // always fills with all of the current attributes.
1793 style = style.replace(/text-decoration:underline;/, '');
1796 // Compute current scroll position
1797 var scrollPos = this.numScrollbackLines -
1798 (this.scrollable.scrollTop-1) / this.cursorHeight;
1800 // Determine original cursor position. Hide cursor temporarily to avoid
1801 // visual artifacts.
1802 var hidden = this.hideCursor();
1803 var cx = this.cursorX;
1804 var cy = this.cursorY;
1805 var console = this.console[this.currentScreen];
1807 if (!incX && !x && w == this.terminalWidth) {
1808 // Scrolling entire lines
1811 if (!this.currentScreen && y == -incY &&
1812 h == this.terminalHeight + incY) {
1813 // Scrolling up with adding to the scrollback buffer. This is only
1814 // possible if there are at least as many lines in the console,
1815 // as the terminal is high
1816 while (console.childNodes.length < this.terminalHeight) {
1817 this.insertBlankLine(this.terminalHeight);
1820 // Add new lines at bottom in order to force scrolling
1821 for (var i = 0; i < y; i++) {
1822 this.insertBlankLine(console.childNodes.length, color, style);
1825 // Adjust the number of lines in the scrollback buffer by
1826 // removing excess entries.
1827 this.updateNumScrollbackLines();
1828 while (this.numScrollbackLines >
1829 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1830 console.removeChild(console.firstChild);
1831 this.numScrollbackLines--;
1834 // Mark lines in the scrollback buffer, so that they do not get
1836 for (var i = this.numScrollbackLines, j = -incY;
1837 i-- > 0 && j-- > 0; ) {
1838 console.childNodes[i].className = 'scrollback';
1841 // Scrolling up without adding to the scrollback buffer.
1844 console.childNodes.length >
1845 this.numScrollbackLines + y + incY; ) {
1846 console.removeChild(console.childNodes[
1847 this.numScrollbackLines + y + incY]);
1850 // If we used to have a scrollback buffer, then we must make sure
1851 // that we add back blank lines at the bottom of the terminal.
1852 // Similarly, if we are scrolling in the middle of the screen,
1853 // we must add blank lines to ensure that the bottom of the screen
1854 // does not move up.
1855 if (this.numScrollbackLines > 0 ||
1856 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1857 for (var i = -incY; i-- > 0; ) {
1858 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1867 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1868 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1870 for (var i = incY; i--; ) {
1871 this.insertBlankLine(this.numScrollbackLines + y, color, style);
1875 // Scrolling partial lines
1877 // Scrolling up or horizontally within a line
1878 for (var i = y + this.numScrollbackLines;
1879 i < y + this.numScrollbackLines + h;
1881 this.copyLineSegment(x + incX, i + incY, x, i, w);
1885 for (var i = y + this.numScrollbackLines + h;
1886 i-- > y + this.numScrollbackLines; ) {
1887 this.copyLineSegment(x + incX, i + incY, x, i, w);
1891 // Clear blank regions
1893 this.clearRegion(x, y, incX, h, color, style);
1894 } else if (incX < 0) {
1895 this.clearRegion(x + w + incX, y, -incX, h, color, style);
1898 this.clearRegion(x, y, w, incY, color, style);
1899 } else if (incY < 0) {
1900 this.clearRegion(x, y + h + incY, w, -incY, color, style);
1904 // Reset scroll position
1905 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1906 this.cursorHeight + 1;
1908 // Move cursor back to its original position
1909 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1913 VT100.prototype.copy = function(selection) {
1914 if (selection == undefined) {
1915 selection = this.selection();
1917 this.internalClipboard = undefined;
1918 if (selection.length) {
1921 this.cliphelper.value = selection;
1922 this.cliphelper.select();
1923 this.cliphelper.createTextRange().execCommand('copy');
1925 this.internalClipboard = selection;
1927 this.cliphelper.value = '';
1931 VT100.prototype.copyLast = function() {
1932 // Opening the context menu can remove the selection. We try to prevent this
1933 // from happening, but that is not possible for all browsers. So, instead,
1934 // we compute the selection before showing the menu.
1935 this.copy(this.lastSelection);
1938 VT100.prototype.pasteFnc = function() {
1939 var clipboard = undefined;
1940 if (this.internalClipboard != undefined) {
1941 clipboard = this.internalClipboard;
1944 this.cliphelper.value = '';
1945 this.cliphelper.createTextRange().execCommand('paste');
1946 clipboard = this.cliphelper.value;
1950 this.cliphelper.value = '';
1951 if (clipboard && this.menu.style.visibility == 'hidden') {
1953 this.keysPressed('' + clipboard);
1960 VT100.prototype.toggleUTF = function() {
1961 this.utfEnabled = !this.utfEnabled;
1963 // We always persist the last value that the user selected. Not necessarily
1964 // the last value that a random program requested.
1965 this.utfPreferred = this.utfEnabled;
1968 VT100.prototype.toggleBell = function() {
1969 this.visualBell = !this.visualBell;
1972 VT100.prototype.toggleCursorBlinking = function() {
1973 this.blinkingCursor = !this.blinkingCursor;
1976 VT100.prototype.about = function() {
1977 alert("VT100 Terminal Emulator " + "2.10 (revision 219)" +
1978 "\nCopyright 2008-2010 by Markus Gutschke\n" +
1979 "For more information check http://shellinabox.com");
1982 VT100.prototype.hideContextMenu = function() {
1983 this.menu.style.visibility = 'hidden';
1984 this.menu.style.top = '-100px';
1985 this.menu.style.left = '-100px';
1986 this.menu.style.width = '0px';
1987 this.menu.style.height = '0px';
1990 VT100.prototype.extendContextMenu = function(entries, actions) {
1993 VT100.prototype.showContextMenu = function(x, y) {
1994 this.menu.innerHTML =
1995 '<table class="popup" ' +
1996 'cellpadding="0" cellspacing="0">' +
1998 '<ul id="menuentries">' +
1999 '<li id="beginclipboard">Copy</li>' +
2000 '<li id="endclipboard">Paste</li>' +
2002 '<li id="reset">Reset</li>' +
2004 '<li id="beginconfig">' +
2005 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2008 (this.visualBell ? '<img src="enabled.gif" />' : '') +
2010 '<li id="endconfig">' +
2011 (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2012 'Blinking Cursor</li>'+
2013 (this.usercss.firstChild ?
2014 '<hr id="beginusercss" />' +
2015 this.usercss.innerHTML +
2016 '<hr id="endusercss" />' :
2018 '<li id="about">About...</li>' +
2023 var popup = this.menu.firstChild;
2024 var menuentries = this.getChildById(popup, 'menuentries');
2026 // Determine menu entries that should be disabled
2027 this.lastSelection = this.selection();
2028 if (!this.lastSelection.length) {
2029 menuentries.firstChild.className
2032 var p = this.pasteFnc();
2034 menuentries.childNodes[1].className
2038 // Actions for default items
2039 var actions = [ this.copyLast, p, this.reset,
2040 this.toggleUTF, this.toggleBell,
2041 this.toggleCursorBlinking ];
2043 // Actions for user CSS styles (if any)
2044 for (var i = 0; i < this.usercssActions.length; ++i) {
2045 actions[actions.length] = this.usercssActions[i];
2047 actions[actions.length] = this.about;
2049 // Allow subclasses to dynamically add entries to the context menu
2050 this.extendContextMenu(menuentries, actions);
2052 // Hook up event listeners
2053 for (var node = menuentries.firstChild, i = 0; node;
2054 node = node.nextSibling) {
2055 if (node.tagName == 'LI') {
2056 if (node.className != 'disabled') {
2057 this.addListener(node, 'mouseover',
2058 function(vt100, node) {
2060 node.className = 'hover';
2063 this.addListener(node, 'mouseout',
2064 function(vt100, node) {
2066 node.className = '';
2069 this.addListener(node, 'mousedown',
2070 function(vt100, action) {
2071 return function(event) {
2072 vt100.hideContextMenu();
2074 vt100.storeUserSettings();
2075 return vt100.cancelEvent(event || window.event);
2077 }(this, actions[i]));
2078 this.addListener(node, 'mouseup',
2080 return function(event) {
2081 return vt100.cancelEvent(event || window.event);
2084 this.addListener(node, 'mouseclick',
2086 return function(event) {
2087 return vt100.cancelEvent(event || window.event);
2095 // Position menu next to the mouse pointer
2096 if (x + popup.clientWidth > this.container.offsetWidth) {
2097 x = this.container.offsetWidth - popup.clientWidth;
2102 if (y + popup.clientHeight > this.container.offsetHeight) {
2103 y = this.container.offsetHeight-popup.clientHeight;
2108 popup.style.left = x + 'px';
2109 popup.style.top = y + 'px';
2111 // Block all other interactions with the terminal emulator
2112 this.menu.style.left = '0px';
2113 this.menu.style.top = '0px';
2114 this.menu.style.width = this.container.offsetWidth + 'px';
2115 this.menu.style.height = this.container.offsetHeight + 'px';
2116 this.addListener(this.menu, 'click', function(vt100) {
2118 vt100.hideContextMenu();
2123 this.menu.style.visibility = '';
2126 VT100.prototype.keysPressed = function(ch) {
2127 for (var i = 0; i < ch.length; i++) {
2128 var c = ch.charCodeAt(i);
2129 this.vt100(c >= 7 && c <= 15 ||
2130 c == 24 || c == 26 || c == 27 || c >= 32
2131 ? String.fromCharCode(c) : '<' + c + '>');
2135 VT100.prototype.applyModifiers = function(ch, event) {
2137 if (event.ctrlKey) {
2138 if (ch >= 32 && ch <= 127) {
2139 // For historic reasons, some control characters are treated specially
2141 case /* 3 */ 51: ch = 27; break;
2142 case /* 4 */ 52: ch = 28; break;
2143 case /* 5 */ 53: ch = 29; break;
2144 case /* 6 */ 54: ch = 30; break;
2145 case /* 7 */ 55: ch = 31; break;
2146 case /* 8 */ 56: ch = 127; break;
2147 case /* ? */ 63: ch = 127; break;
2148 default: ch &= 31; break;
2152 return String.fromCharCode(ch);
2158 VT100.prototype.handleKey = function(event) {
2159 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2160 // (event.shiftKey || event.ctrlKey || event.altKey ||
2161 // event.metaKey ? ', ' +
2162 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2163 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2166 if (typeof event.charCode != 'undefined') {
2167 // non-IE keypress events have a translated charCode value. Also, our
2168 // fake events generated when receiving keydown events include this data
2170 ch = event.charCode;
2171 key = event.keyCode;
2173 // When sending a keypress event, IE includes the translated character
2174 // code in the keyCode field.
2179 // Apply modifier keys (ctrl and shift)
2183 ch = this.applyModifiers(ch, event);
2185 // By this point, "ch" is either defined and contains the character code, or
2186 // it is undefined and "key" defines the code of a function key
2187 if (ch != undefined) {
2188 this.scrollable.scrollTop = this.numScrollbackLines *
2189 this.cursorHeight + 1;
2191 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2192 // Many programs have difficulties dealing with parametrized escape
2193 // sequences for function keys. Thus, if ALT is the only modifier
2194 // key, return Emacs-style keycodes for commonly used keys.
2196 case 33: /* Page Up */ ch = '\u001B<'; break;
2197 case 34: /* Page Down */ ch = '\u001B>'; break;
2198 case 37: /* Left */ ch = '\u001Bb'; break;
2199 case 38: /* Up */ ch = '\u001Bp'; break;
2200 case 39: /* Right */ ch = '\u001Bf'; break;
2201 case 40: /* Down */ ch = '\u001Bn'; break;
2202 case 46: /* Delete */ ch = '\u001Bd'; break;
2205 } else if (event.shiftKey && !event.ctrlKey &&
2206 !event.altKey && !event.metaKey) {
2208 case 33: /* Page Up */ this.scrollBack(); return;
2209 case 34: /* Page Down */ this.scrollFore(); return;
2213 if (ch == undefined) {
2215 case 8: /* Backspace */ ch = '\u007f'; break;
2216 case 9: /* Tab */ ch = '\u0009'; break;
2217 case 10: /* Return */ ch = '\u000A'; break;
2218 case 13: /* Enter */ ch = this.crLfMode ?
2219 '\r\n' : '\r'; break;
2220 case 16: /* Shift */ return;
2221 case 17: /* Ctrl */ return;
2222 case 18: /* Alt */ return;
2223 case 19: /* Break */ return;
2224 case 20: /* Caps Lock */ return;
2225 case 27: /* Escape */ ch = '\u001B'; break;
2226 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2227 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2228 case 35: /* End */ ch = '\u001BOF'; break;
2229 case 36: /* Home */ ch = '\u001BOH'; break;
2230 case 37: /* Left */ ch = this.cursorKeyMode ?
2231 '\u001BOD' : '\u001B[D'; break;
2232 case 38: /* Up */ ch = this.cursorKeyMode ?
2233 '\u001BOA' : '\u001B[A'; break;
2234 case 39: /* Right */ ch = this.cursorKeyMode ?
2235 '\u001BOC' : '\u001B[C'; break;
2236 case 40: /* Down */ ch = this.cursorKeyMode ?
2237 '\u001BOB' : '\u001B[B'; break;
2238 case 45: /* Insert */ ch = '\u001B[2~'; break;
2239 case 46: /* Delete */ ch = '\u001B[3~'; break;
2240 case 91: /* Left Window */ return;
2241 case 92: /* Right Window */ return;
2242 case 93: /* Select */ return;
2243 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2244 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2245 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2246 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2247 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2248 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2249 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2250 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2251 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2252 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2253 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2254 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2255 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2256 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2257 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2258 case 112: /* F1 */ ch = '\u001BOP'; break;
2259 case 113: /* F2 */ ch = '\u001BOQ'; break;
2260 case 114: /* F3 */ ch = '\u001BOR'; break;
2261 case 115: /* F4 */ ch = '\u001BOS'; break;
2262 case 116: /* F5 */ ch = '\u001B[15~'; break;
2263 case 117: /* F6 */ ch = '\u001B[17~'; break;
2264 case 118: /* F7 */ ch = '\u001B[18~'; break;
2265 case 119: /* F8 */ ch = '\u001B[19~'; break;
2266 case 120: /* F9 */ ch = '\u001B[20~'; break;
2267 case 121: /* F10 */ ch = '\u001B[21~'; break;
2268 case 122: /* F11 */ ch = '\u001B[23~'; break;
2269 case 123: /* F12 */ ch = '\u001B[24~'; break;
2270 case 144: /* Num Lock */ return;
2271 case 145: /* Scroll Lock */ return;
2272 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2273 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2274 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2275 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2276 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2277 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2278 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2279 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2280 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2281 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2282 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2285 this.scrollable.scrollTop = this.numScrollbackLines *
2286 this.cursorHeight + 1;
2290 // "ch" now contains the sequence of keycodes to send. But we might still
2291 // have to apply the effects of modifier keys.
2292 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2293 var start, digit, part1, part2;
2294 if ((start = ch.substr(0, 2)) == '\u001B[') {
2296 part1.length < ch.length &&
2297 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2298 part1 = ch.substr(0, part1.length + 1);
2300 part2 = ch.substr(part1.length);
2301 if (part1.length > 2) {
2304 } else if (start == '\u001BO') {
2306 part2 = ch.substr(2);
2308 if (part1 != undefined) {
2310 ((event.shiftKey ? 1 : 0) +
2311 (event.altKey|event.metaKey ? 2 : 0) +
2312 (event.ctrlKey ? 4 : 0)) +
2314 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2319 if (this.menu.style.visibility == 'hidden') {
2320 // this.vt100('R: c=');
2321 // for (var i = 0; i < ch.length; i++)
2322 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2323 // this.vt100('\r\n');
2324 this.keysPressed(ch);
2328 VT100.prototype.inspect = function(o, d) {
2329 if (d == undefined) {
2333 if (typeof o == 'object' && ++d < 2) {
2336 rc += this.spaces(d * 2) + i + ' -> ';
2338 rc += this.inspect(o[i], d);
2340 rc += '?' + '?' + '?\r\n';
2345 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2350 VT100.prototype.checkComposedKeys = function(event) {
2351 // Composed keys (at least on Linux) do not generate normal events.
2352 // Instead, they get entered into the text field. We normally catch
2353 // this on the next keyup event.
2354 var s = this.input.value;
2356 this.input.value = '';
2357 if (this.menu.style.visibility == 'hidden') {
2358 this.keysPressed(s);
2363 VT100.prototype.fixEvent = function(event) {
2364 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2365 // is used as a second-level selector, clear the modifier bits before
2366 // handling the event.
2367 if (event.ctrlKey && event.altKey) {
2369 fake.charCode = event.charCode;
2370 fake.keyCode = event.keyCode;
2371 fake.ctrlKey = false;
2372 fake.shiftKey = event.shiftKey;
2373 fake.altKey = false;
2374 fake.metaKey = event.metaKey;
2378 // Some browsers fail to translate keys, if both shift and alt/meta is
2379 // pressed at the same time. We try to translate those cases, but that
2380 // only works for US keyboard layouts.
2381 if (event.shiftKey) {
2384 switch (this.lastNormalKeyDownEvent.keyCode) {
2385 case 39: /* ' -> " */ u = 39; s = 34; break;
2386 case 44: /* , -> < */ u = 44; s = 60; break;
2387 case 45: /* - -> _ */ u = 45; s = 95; break;
2388 case 46: /* . -> > */ u = 46; s = 62; break;
2389 case 47: /* / -> ? */ u = 47; s = 63; break;
2391 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2392 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2393 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2394 case 51: /* 3 -> # */ u = 51; s = 35; break;
2395 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2396 case 53: /* 5 -> % */ u = 53; s = 37; break;
2397 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2398 case 55: /* 7 -> & */ u = 55; s = 38; break;
2399 case 56: /* 8 -> * */ u = 56; s = 42; break;
2400 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2402 case 59: /* ; -> : */ u = 59; s = 58; break;
2403 case 61: /* = -> + */ u = 61; s = 43; break;
2404 case 91: /* [ -> { */ u = 91; s = 123; break;
2405 case 92: /* \ -> | */ u = 92; s = 124; break;
2406 case 93: /* ] -> } */ u = 93; s = 125; break;
2407 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2409 case 109: /* - -> _ */ u = 45; s = 95; break;
2410 case 111: /* / -> ? */ u = 47; s = 63; break;
2412 case 186: /* ; -> : */ u = 59; s = 58; break;
2413 case 187: /* = -> + */ u = 61; s = 43; break;
2414 case 188: /* , -> < */ u = 44; s = 60; break;
2415 case 189: /* - -> _ */ u = 45; s = 95; break;
2416 case 190: /* . -> > */ u = 46; s = 62; break;
2417 case 191: /* / -> ? */ u = 47; s = 63; break;
2418 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2419 case 219: /* [ -> { */ u = 91; s = 123; break;
2420 case 220: /* \ -> | */ u = 92; s = 124; break;
2421 case 221: /* ] -> } */ u = 93; s = 125; break;
2422 case 222: /* ' -> " */ u = 39; s = 34; break;
2425 if (s && (event.charCode == u || event.charCode == 0)) {
2428 fake.keyCode = event.keyCode;
2429 fake.ctrlKey = event.ctrlKey;
2430 fake.shiftKey = event.shiftKey;
2431 fake.altKey = event.altKey;
2432 fake.metaKey = event.metaKey;
2439 VT100.prototype.keyDown = function(event) {
2440 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2441 // (event.shiftKey || event.ctrlKey || event.altKey ||
2442 // event.metaKey ? ', ' +
2443 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2444 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2446 this.checkComposedKeys(event);
2447 this.lastKeyPressedEvent = undefined;
2448 this.lastKeyDownEvent = undefined;
2449 this.lastNormalKeyDownEvent = event;
2452 event.keyCode == 32 ||
2453 event.keyCode >= 48 && event.keyCode <= 57 ||
2454 event.keyCode >= 65 && event.keyCode <= 90;
2457 event.keyCode >= 96 && event.keyCode <= 105 ||
2458 event.keyCode == 226;
2461 event.keyCode == 59 || event.keyCode == 61 ||
2462 event.keyCode == 106 || event.keyCode == 107 ||
2463 event.keyCode >= 109 && event.keyCode <= 111 ||
2464 event.keyCode >= 186 && event.keyCode <= 192 ||
2465 event.keyCode >= 219 && event.keyCode <= 223 ||
2466 event.keyCode == 252;
2468 if (navigator.appName == 'Konqueror') {
2469 normalKey |= event.keyCode < 128;
2474 // We normally prefer to look at keypress events, as they perform the
2475 // translation from keyCode to charCode. This is important, as the
2476 // translation is locale-dependent.
2477 // But for some keys, we must intercept them during the keydown event,
2478 // as they would otherwise get interpreted by the browser.
2479 // Even, when doing all of this, there are some keys that we can never
2480 // intercept. This applies to some of the menu navigation keys in IE.
2481 // In fact, we see them, but we cannot stop IE from seeing them, too.
2482 if ((event.charCode || event.keyCode) &&
2483 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2485 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2486 // interpret this sequence ourselves, as some keyboard layouts use
2487 // it for second-level layouts.
2488 !(event.ctrlKey && event.altKey)) ||
2489 this.catchModifiersEarly && normalKey && !alphNumKey &&
2490 (event.ctrlKey || event.altKey || event.metaKey) ||
2492 this.lastKeyDownEvent = event;
2494 fake.ctrlKey = event.ctrlKey;
2495 fake.shiftKey = event.shiftKey;
2496 fake.altKey = event.altKey;
2497 fake.metaKey = event.metaKey;
2499 fake.charCode = event.keyCode;
2503 fake.keyCode = event.keyCode;
2504 if (!alphNumKey && event.shiftKey) {
2505 fake = this.fixEvent(fake);
2509 this.handleKey(fake);
2510 this.lastNormalKeyDownEvent = undefined;
2513 // For non-IE browsers
2514 event.stopPropagation();
2515 event.preventDefault();
2520 event.cancelBubble = true;
2521 event.returnValue = false;
2531 VT100.prototype.keyPressed = function(event) {
2532 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2533 // (event.shiftKey || event.ctrlKey || event.altKey ||
2534 // event.metaKey ? ', ' +
2535 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2536 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2538 if (this.lastKeyDownEvent) {
2539 // If we already processed the key on keydown, do not process it
2540 // again here. Ideally, the browser should not even have generated a
2541 // keypress event in this case. But that does not appear to always work.
2542 this.lastKeyDownEvent = undefined;
2544 this.handleKey(event.altKey || event.metaKey
2545 ? this.fixEvent(event) : event);
2549 // For non-IE browsers
2550 event.preventDefault();
2556 event.cancelBubble = true;
2557 event.returnValue = false;
2562 this.lastNormalKeyDownEvent = undefined;
2563 this.lastKeyPressedEvent = event;
2567 VT100.prototype.keyUp = function(event) {
2568 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2569 // (event.shiftKey || event.ctrlKey || event.altKey ||
2570 // event.metaKey ? ', ' +
2571 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2572 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2574 if (this.lastKeyPressedEvent) {
2575 // The compose key on Linux occasionally confuses the browser and keeps
2576 // inserting bogus characters into the input field, even if just a regular
2577 // key has been pressed. Detect this case and drop the bogus characters.
2579 event.srcElement).value = '';
2581 // This is usually were we notice that a key has been composed and
2582 // thus failed to generate normal events.
2583 this.checkComposedKeys(event);
2585 // Some browsers don't report keypress events if ctrl or alt is pressed
2586 // for non-alphanumerical keys. Patch things up for now, but in the
2587 // future we will catch these keys earlier (in the keydown handler).
2588 if (this.lastNormalKeyDownEvent) {
2589 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
2590 this.catchModifiersEarly = true;
2592 event.keyCode == 32 ||
2593 event.keyCode >= 48 && event.keyCode <= 57 ||
2594 event.keyCode >= 65 && event.keyCode <= 90;
2597 event.keyCode >= 96 && event.keyCode <= 105;
2600 event.keyCode == 59 || event.keyCode == 61 ||
2601 event.keyCode == 106 || event.keyCode == 107 ||
2602 event.keyCode >= 109 && event.keyCode <= 111 ||
2603 event.keyCode >= 186 && event.keyCode <= 192 ||
2604 event.keyCode >= 219 && event.keyCode <= 223 ||
2605 event.keyCode == 252;
2607 fake.ctrlKey = event.ctrlKey;
2608 fake.shiftKey = event.shiftKey;
2609 fake.altKey = event.altKey;
2610 fake.metaKey = event.metaKey;
2612 fake.charCode = event.keyCode;
2616 fake.keyCode = event.keyCode;
2617 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2618 fake = this.fixEvent(fake);
2621 this.lastNormalKeyDownEvent = undefined;
2622 this.handleKey(fake);
2628 event.cancelBubble = true;
2629 event.returnValue = false;
2634 this.lastKeyDownEvent = undefined;
2635 this.lastKeyPressedEvent = undefined;
2639 VT100.prototype.animateCursor = function(inactive) {
2640 if (!this.cursorInterval) {
2641 this.cursorInterval = setInterval(
2644 vt100.animateCursor();
2646 // Use this opportunity to check whether the user entered a composed
2647 // key, or whether somebody pasted text into the textfield.
2648 vt100.checkComposedKeys();
2652 if (inactive != undefined || this.cursor.className != 'inactive') {
2654 this.cursor.className = 'inactive';
2656 if (this.blinkingCursor) {
2657 this.cursor.className = this.cursor.className == 'bright'
2660 this.cursor.className = 'bright';
2666 VT100.prototype.blurCursor = function() {
2667 this.animateCursor(true);
2670 VT100.prototype.focusCursor = function() {
2671 this.animateCursor(false);
2674 VT100.prototype.flashScreen = function() {
2675 this.isInverted = !this.isInverted;
2676 this.refreshInvertedState();
2677 this.isInverted = !this.isInverted;
2678 setTimeout(function(vt100) {
2680 vt100.refreshInvertedState();
2685 VT100.prototype.beep = function() {
2686 if (this.visualBell) {
2693 this.beeper.src = 'beep.wav';
2700 VT100.prototype.bs = function() {
2701 if (this.cursorX > 0) {
2702 this.gotoXY(this.cursorX - 1, this.cursorY);
2703 this.needWrap = false;
2707 VT100.prototype.ht = function(count) {
2708 if (count == undefined) {
2711 var cx = this.cursorX;
2712 while (count-- > 0) {
2713 while (cx++ < this.terminalWidth) {
2714 var tabState = this.userTabStop[cx];
2715 if (tabState == false) {
2716 // Explicitly cleared tab stop
2718 } else if (tabState) {
2719 // Explicitly set tab stop
2722 // Default tab stop at each eighth column
2729 if (cx > this.terminalWidth - 1) {
2730 cx = this.terminalWidth - 1;
2732 if (cx != this.cursorX) {
2733 this.gotoXY(cx, this.cursorY);
2737 VT100.prototype.rt = function(count) {
2738 if (count == undefined) {
2741 var cx = this.cursorX;
2742 while (count-- > 0) {
2744 var tabState = this.userTabStop[cx];
2745 if (tabState == false) {
2746 // Explicitly cleared tab stop
2748 } else if (tabState) {
2749 // Explicitly set tab stop
2752 // Default tab stop at each eighth column
2762 if (cx != this.cursorX) {
2763 this.gotoXY(cx, this.cursorY);
2767 VT100.prototype.cr = function() {
2768 this.gotoXY(0, this.cursorY);
2769 this.needWrap = false;
2772 VT100.prototype.lf = function(count) {
2773 if (count == undefined) {
2776 if (count > this.terminalHeight) {
2777 count = this.terminalHeight;
2783 while (count-- > 0) {
2784 if (this.cursorY == this.bottom - 1) {
2785 this.scrollRegion(0, this.top + 1,
2786 this.terminalWidth, this.bottom - this.top - 1,
2787 0, -1, this.color, this.style);
2789 } else if (this.cursorY < this.terminalHeight - 1) {
2790 this.gotoXY(this.cursorX, this.cursorY + 1);
2795 VT100.prototype.ri = function(count) {
2796 if (count == undefined) {
2799 if (count > this.terminalHeight) {
2800 count = this.terminalHeight;
2806 while (count-- > 0) {
2807 if (this.cursorY == this.top) {
2808 this.scrollRegion(0, this.top,
2809 this.terminalWidth, this.bottom - this.top - 1,
2810 0, 1, this.color, this.style);
2811 } else if (this.cursorY > 0) {
2812 this.gotoXY(this.cursorX, this.cursorY - 1);
2815 this.needWrap = false;
2818 VT100.prototype.respondID = function() {
2819 this.respondString += '\u001B[?6c';
2822 VT100.prototype.respondSecondaryDA = function() {
2823 this.respondString += '\u001B[>0;0;0c';
2827 VT100.prototype.updateStyle = function() {
2829 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2830 this.style = 'text-decoration:underline;';
2832 var bg = (this.attr >> 4) & 0xF;
2833 var fg = this.attr & 0xF;
2834 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2839 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2840 fg = 8; // Dark grey
2841 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2844 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2847 // Make some readability enhancements. Most notably, disallow identical
2848 // background and foreground colors.
2850 if ((fg ^= 8) == 7) {
2854 // And disallow bright colors on a light-grey background.
2855 if (bg == 7 && fg >= 8) {
2856 if ((fg -= 8) == 7) {
2861 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2864 VT100.prototype.setAttrColors = function(attr) {
2865 if (attr != this.attr) {
2871 VT100.prototype.saveCursor = function() {
2872 this.savedX[this.currentScreen] = this.cursorX;
2873 this.savedY[this.currentScreen] = this.cursorY;
2874 this.savedAttr[this.currentScreen] = this.attr;
2875 this.savedUseGMap = this.useGMap;
2876 for (var i = 0; i < 4; i++) {
2877 this.savedGMap[i] = this.GMap[i];
2879 this.savedValid[this.currentScreen] = true;
2882 VT100.prototype.restoreCursor = function() {
2883 if (!this.savedValid[this.currentScreen]) {
2886 this.attr = this.savedAttr[this.currentScreen];
2888 this.useGMap = this.savedUseGMap;
2889 for (var i = 0; i < 4; i++) {
2890 this.GMap[i] = this.savedGMap[i];
2892 this.translate = this.GMap[this.useGMap];
2893 this.needWrap = false;
2894 this.gotoXY(this.savedX[this.currentScreen],
2895 this.savedY[this.currentScreen]);
2898 VT100.prototype.set80_132Mode = function(state) {
2899 var transform = undefined;
2900 var styles = [ 'transform',
2905 for (var i = 0; i < styles.length; ++i) {
2906 if (typeof this.console[0].style[styles[i]] != 'undefined') {
2907 transform = styles[i];
2913 if ((this.console[this.currentScreen].style[transform] != '') == state) {
2917 state ? transform == 'filter'
2918 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
2919 'M11=0.606060606060606060606,M12=0,M21=0,M22=1,' +
2920 "sizingMethod='auto expand')"
2921 : 'translateX(-50%) ' +
2922 'scaleX(0.606060606060606060606) ' +
2925 this.console[this.currentScreen].style[transform] = style;
2926 this.cursor.style[transform] = style;
2927 this.space.style[transform] = style;
2928 this.scale = state ? 1.65 : 1.0;
2929 if (transform == 'filter') {
2930 this.console[this.currentScreen].style.width = state ? '165%' : '';
2936 VT100.prototype.setMode = function(state) {
2937 for (var i = 0; i <= this.npar; i++) {
2938 if (this.isQuestionMark) {
2939 switch (this.par[i]) {
2940 case 1: this.cursorKeyMode = state; break;
2941 case 3: this.set80_132Mode(state); break;
2942 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2943 case 6: this.offsetMode = state; break;
2944 case 7: this.autoWrapMode = state; break;
2946 case 9: this.mouseReporting = state; break;
2947 case 25: this.cursorNeedsShowing = state;
2948 if (state) { this.showCursor(); }
2949 else { this.hideCursor(); } break;
2952 case 47: this.enableAlternateScreen(state); break;
2956 switch (this.par[i]) {
2957 case 3: this.dispCtrl = state; break;
2958 case 4: this.insertMode = state; break;
2959 case 20:this.crLfMode = state; break;
2966 VT100.prototype.statusReport = function() {
2967 // Ready and operational.
2968 this.respondString += '\u001B[0n';
2971 VT100.prototype.cursorReport = function() {
2972 this.respondString += '\u001B[' +
2973 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2975 (this.cursorX + 1) +
2979 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2980 // Changing of cursor color is not implemented.
2983 VT100.prototype.openPrinterWindow = function() {
2986 if (!this.printWin || this.printWin.closed) {
2987 this.printWin = window.open('', 'print-output',
2988 'width=800,height=600,directories=no,location=no,menubar=yes,' +
2989 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
2990 this.printWin.document.body.innerHTML =
2991 '<link rel="stylesheet" href="' +
2992 document.location.protocol + '//' + document.location.host +
2993 document.location.pathname.replace(/[^/]*$/, '') +
2994 'print-styles.css" type="text/css">\n' +
2995 '<div id="options"><input id="autoprint" type="checkbox"' +
2996 (this.autoprint ? ' checked' : '') + '>' +
2997 'Automatically, print page(s) when job is ready' +
2998 '</input></div>\n' +
2999 '<div id="spacer"><input type="checkbox"> </input></div>' +
3000 '<pre id="print"></pre>\n';
3001 var autoprint = this.printWin.document.getElementById('autoprint');
3002 this.addListener(autoprint, 'click',
3003 (function(vt100, autoprint) {
3005 vt100.autoprint = autoprint.checked;
3006 vt100.storeUserSettings();
3009 })(this, autoprint));
3010 this.printWin.document.title = 'ShellInABox Printer Output';
3013 // Maybe, a popup blocker prevented us from working. Better catch the
3014 // exception, so that we won't break the entire terminal session. The
3015 // user probably needs to disable the blocker first before retrying the
3019 rc &= this.printWin && !this.printWin.closed &&
3020 (this.printWin.innerWidth ||
3021 this.printWin.document.documentElement.clientWidth ||
3022 this.printWin.document.body.clientWidth) > 1;
3024 if (!rc && this.printing == 100) {
3025 // Different popup blockers work differently. We try to detect a couple
3026 // of common methods. And then we retry again a brief amount later, as
3027 // false positives are otherwise possible. If we are sure that there is
3028 // a popup blocker in effect, we alert the user to it. This is helpful
3029 // as some popup blockers have minimal or no UI, and the user might not
3030 // notice that they are missing the popup. In any case, we only show at
3031 // most one message per print job.
3032 this.printing = true;
3033 setTimeout((function(win) {
3035 if (!win || win.closed ||
3037 win.document.documentElement.clientWidth ||
3038 win.document.body.clientWidth) <= 1) {
3039 alert('Attempted to print, but a popup blocker ' +
3040 'prevented the printer window from opening');
3043 })(this.printWin), 2000);
3048 VT100.prototype.sendToPrinter = function(s) {
3049 this.openPrinterWindow();
3051 var doc = this.printWin.document;
3052 var print = doc.getElementById('print');
3053 if (print.lastChild && print.lastChild.nodeName == '#text') {
3054 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3056 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3059 // There probably was a more aggressive popup blocker that prevented us
3060 // from accessing the printer windows.
3064 VT100.prototype.sendControlToPrinter = function(ch) {
3065 // We get called whenever doControl() is active. But for the printer, we
3066 // only implement a basic line printer that doesn't understand most of
3067 // the escape sequences of the VT100 terminal. In fact, the only escape
3068 // sequence that we really need to recognize is '^[[5i' for turning the
3074 this.openPrinterWindow();
3075 var doc = this.printWin.document;
3076 var print = doc.getElementById('print');
3077 var chars = print.lastChild &&
3078 print.lastChild.nodeName == '#text' ?
3079 print.lastChild.textContent.length : 0;
3080 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3087 this.openPrinterWindow();
3088 var pageBreak = this.printWin.document.createElement('div');
3089 pageBreak.className = 'pagebreak';
3090 pageBreak.innerHTML = '<hr />';
3091 this.printWin.document.getElementById('print').appendChild(pageBreak);
3095 this.openPrinterWindow();
3096 var lineBreak = this.printWin.document.createElement('br');
3097 this.printWin.document.getElementById('print').appendChild(lineBreak);
3101 this.isEsc = 1 /* ESesc */;
3104 switch (this.isEsc) {
3106 this.isEsc = 0 /* ESnormal */;
3109 this.isEsc = 2 /* ESsquare */;
3115 case 2 /* ESsquare */:
3117 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3118 0, 0, 0, 0, 0, 0, 0, 0 ];
3119 this.isEsc = 3 /* ESgetpars */;
3120 this.isQuestionMark = ch == 0x3F /*?*/;
3121 if (this.isQuestionMark) {
3125 case 3 /* ESgetpars */:
3126 if (ch == 0x3B /*;*/) {
3129 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3130 var par = this.par[this.npar];
3131 if (par == undefined) {
3134 this.par[this.npar] = 10*par + (ch & 0xF);
3137 this.isEsc = 4 /* ESgotpars */;
3140 case 4 /* ESgotpars */:
3141 this.isEsc = 0 /* ESnormal */;
3142 if (this.isQuestionMark) {
3147 this.csii(this.par[0]);
3154 this.isEsc = 0 /* ESnormal */;
3160 // There probably was a more aggressive popup blocker that prevented us
3161 // from accessing the printer windows.
3165 VT100.prototype.csiAt = function(number) {
3170 if (number > this.terminalWidth - this.cursorX) {
3171 number = this.terminalWidth - this.cursorX;
3173 this.scrollRegion(this.cursorX, this.cursorY,
3174 this.terminalWidth - this.cursorX - number, 1,
3175 number, 0, this.color, this.style);
3176 this.needWrap = false;
3179 VT100.prototype.csii = function(number) {
3182 case 0: // Print Screen
3185 case 4: // Stop printing
3187 if (this.printing && this.printWin && !this.printWin.closed) {
3188 var print = this.printWin.document.getElementById('print');
3189 while (print.lastChild &&
3190 print.lastChild.tagName == 'DIV' &&
3191 print.lastChild.className == 'pagebreak') {
3192 // Remove trailing blank pages
3193 print.removeChild(print.lastChild);
3195 if (this.autoprint) {
3196 this.printWin.print();
3201 this.printing = false;
3203 case 5: // Start printing
3204 if (!this.printing && this.printWin && !this.printWin.closed) {
3205 this.printWin.document.getElementById('print').innerHTML = '';
3207 this.printing = 100;
3214 VT100.prototype.csiJ = function(number) {
3216 case 0: // Erase from cursor to end of display
3217 this.clearRegion(this.cursorX, this.cursorY,
3218 this.terminalWidth - this.cursorX, 1,
3219 this.color, this.style);
3220 if (this.cursorY < this.terminalHeight-2) {
3221 this.clearRegion(0, this.cursorY+1,
3222 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3223 this.color, this.style);
3226 case 1: // Erase from start to cursor
3227 if (this.cursorY > 0) {
3228 this.clearRegion(0, 0,
3229 this.terminalWidth, this.cursorY,
3230 this.color, this.style);
3232 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3233 this.color, this.style);
3235 case 2: // Erase whole display
3236 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3237 this.color, this.style);
3245 VT100.prototype.csiK = function(number) {
3247 case 0: // Erase from cursor to end of line
3248 this.clearRegion(this.cursorX, this.cursorY,
3249 this.terminalWidth - this.cursorX, 1,
3250 this.color, this.style);
3252 case 1: // Erase from start of line to cursor
3253 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3254 this.color, this.style);
3256 case 2: // Erase whole line
3257 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3258 this.color, this.style);
3266 VT100.prototype.csiL = function(number) {
3267 // Open line by inserting blank line(s)
3268 if (this.cursorY >= this.bottom) {
3274 if (number > this.bottom - this.cursorY) {
3275 number = this.bottom - this.cursorY;
3277 this.scrollRegion(0, this.cursorY,
3278 this.terminalWidth, this.bottom - this.cursorY - number,
3279 0, number, this.color, this.style);
3283 VT100.prototype.csiM = function(number) {
3284 // Delete line(s), scrolling up the bottom of the screen.
3285 if (this.cursorY >= this.bottom) {
3291 if (number > this.bottom - this.cursorY) {
3292 number = bottom - cursorY;
3294 this.scrollRegion(0, this.cursorY + number,
3295 this.terminalWidth, this.bottom - this.cursorY - number,
3296 0, -number, this.color, this.style);
3300 VT100.prototype.csim = function() {
3301 for (var i = 0; i <= this.npar; i++) {
3302 switch (this.par[i]) {
3303 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
3304 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
3305 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
3306 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
3307 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
3308 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
3310 this.translate = this.GMap[this.useGMap];
3311 this.dispCtrl = false;
3312 this.toggleMeta = false;
3315 this.translate = this.CodePage437Map;
3316 this.dispCtrl = true;
3317 this.toggleMeta = false;
3320 this.translate = this.CodePage437Map;
3321 this.dispCtrl = true;
3322 this.toggleMeta = true;
3325 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
3326 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
3327 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
3328 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
3329 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
3330 0x0200 /* ATTR_UNDERLINE */; break;
3331 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
3332 case 49: this.attr |= 0xF0; break;
3334 if (this.par[i] >= 30 && this.par[i] <= 37) {
3335 var fg = this.par[i] - 30;
3336 this.attr = (this.attr & ~0x0F) | fg;
3337 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3338 var bg = this.par[i] - 40;
3339 this.attr = (this.attr & ~0xF0) | (bg << 4);
3347 VT100.prototype.csiP = function(number) {
3348 // Delete character(s) following cursor
3352 if (number > this.terminalWidth - this.cursorX) {
3353 number = this.terminalWidth - this.cursorX;
3355 this.scrollRegion(this.cursorX + number, this.cursorY,
3356 this.terminalWidth - this.cursorX - number, 1,
3357 -number, 0, this.color, this.style);
3361 VT100.prototype.csiX = function(number) {
3362 // Clear characters following cursor
3366 if (number > this.terminalWidth - this.cursorX) {
3367 number = this.terminalWidth - this.cursorX;
3369 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3370 this.color, this.style);
3374 VT100.prototype.settermCommand = function() {
3375 // Setterm commands are not implemented
3378 VT100.prototype.doControl = function(ch) {
3379 if (this.printing) {
3380 this.sendControlToPrinter(ch);
3385 case 0x00: /* ignored */ break;
3386 case 0x08: this.bs(); break;
3387 case 0x09: this.ht(); break;
3391 case 0x84: this.lf(); if (!this.crLfMode) break;
3392 case 0x0D: this.cr(); break;
3393 case 0x85: this.cr(); this.lf(); break;
3394 case 0x0E: this.useGMap = 1;
3395 this.translate = this.GMap[1];
3396 this.dispCtrl = true; break;
3397 case 0x0F: this.useGMap = 0;
3398 this.translate = this.GMap[0];
3399 this.dispCtrl = false; break;
3401 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3402 case 0x1B: this.isEsc = 1 /* ESesc */; break;
3403 case 0x7F: /* ignored */ break;
3404 case 0x88: this.userTabStop[this.cursorX] = true; break;
3405 case 0x8D: this.ri(); break;
3406 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3407 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3408 case 0x9A: this.respondID(); break;
3409 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3410 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
3414 default: switch (this.isEsc) {
3416 this.isEsc = 0 /* ESnormal */;
3418 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3419 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3421 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3423 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3425 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3426 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3427 /*7*/ case 0x37: this.saveCursor(); break;
3428 /*8*/ case 0x38: this.restoreCursor(); break;
3429 /*>*/ case 0x3E: this.applKeyMode = false; break;
3430 /*=*/ case 0x3D: this.applKeyMode = true; break;
3431 /*D*/ case 0x44: this.lf(); break;
3432 /*E*/ case 0x45: this.cr(); this.lf(); break;
3433 /*M*/ case 0x4D: this.ri(); break;
3434 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3435 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3436 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3437 /*Z*/ case 0x5A: this.respondID(); break;
3438 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3439 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3440 /*c*/ case 0x63: this.reset(); break;
3441 /*g*/ case 0x67: this.flashScreen(); break;
3445 case 15 /* ESnonstd */:
3449 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3450 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3451 this.isEsc = 16 /* ESpalette */; break;
3452 /*R*/ case 0x52: // Palette support is not implemented
3453 this.isEsc = 0 /* ESnormal */; break;
3454 default: this.isEsc = 0 /* ESnormal */; break;
3457 case 16 /* ESpalette */:
3458 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3459 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3460 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3461 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3463 if (this.npar == 7) {
3464 // Palette support is not implemented
3465 this.isEsc = 0 /* ESnormal */;
3468 this.isEsc = 0 /* ESnormal */;
3471 case 2 /* ESsquare */:
3473 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3474 0, 0, 0, 0, 0, 0, 0, 0 ];
3475 this.isEsc = 3 /* ESgetpars */;
3476 /*[*/ if (ch == 0x5B) { // Function key
3477 this.isEsc = 6 /* ESfunckey */;
3480 /*?*/ this.isQuestionMark = ch == 0x3F;
3481 if (this.isQuestionMark) {
3486 case 5 /* ESdeviceattr */:
3487 case 3 /* ESgetpars */:
3488 /*;*/ if (ch == 0x3B) {
3491 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3492 var par = this.par[this.npar];
3493 if (par == undefined) {
3496 this.par[this.npar] = 10*par + (ch & 0xF);
3498 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3500 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3501 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3502 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3503 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3506 this.isEsc = 0 /* ESnormal */;
3509 this.isEsc = 4 /* ESgotpars */;
3512 case 4 /* ESgotpars */:
3513 this.isEsc = 0 /* ESnormal */;
3514 if (this.isQuestionMark) {
3516 /*h*/ case 0x68: this.setMode(true); break;
3517 /*l*/ case 0x6C: this.setMode(false); break;
3518 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3521 this.isQuestionMark = false;
3525 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3526 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3528 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3529 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3530 this.cursorY - (this.par[0] ? this.par[0] : 1));
3533 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3534 this.cursorY + (this.par[0] ? this.par[0] : 1));
3537 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3538 this.cursorY); break;
3539 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3540 this.cursorY); break;
3541 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3543 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3545 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3547 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3548 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3549 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3550 /*i*/ case 0x69: this.csii(this.par[0]); break;
3551 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3552 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3553 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3554 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3555 /*m*/ case 0x6D: this.csim(); break;
3556 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3557 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3558 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3559 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3560 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3561 /*g*/ case 0x67: if (this.par[0] == 0) {
3562 this.userTabStop[this.cursorX] = false;
3563 } else if (this.par[0] == 2 || this.par[0] == 3) {
3564 this.userTabStop = [ ];
3565 for (var i = 0; i < this.terminalWidth; i++) {
3566 this.userTabStop[i] = false;
3570 /*h*/ case 0x68: this.setMode(true); break;
3571 /*l*/ case 0x6C: this.setMode(false); break;
3572 /*n*/ case 0x6E: switch (this.par[0]) {
3573 case 5: this.statusReport(); break;
3574 case 6: this.cursorReport(); break;
3578 /*q*/ case 0x71: // LED control not implemented
3580 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3581 var b = this.par[1] ? this.par[1]
3582 : this.terminalHeight;
3583 if (t < b && b <= this.terminalHeight) {
3589 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3590 if (c > this.terminalWidth * this.terminalHeight) {
3591 c = this.terminalWidth * this.terminalHeight;
3594 lineBuf += this.lastCharacter;
3597 /*s*/ case 0x73: this.saveCursor(); break;
3598 /*u*/ case 0x75: this.restoreCursor(); break;
3599 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3600 /*]*/ case 0x5D: this.settermCommand(); break;
3604 case 12 /* ESbang */:
3608 this.isEsc = 0 /* ESnormal */;
3610 case 13 /* ESpercent */:
3611 this.isEsc = 0 /* ESnormal */;
3613 /*@*/ case 0x40: this.utfEnabled = false; break;
3615 /*8*/ case 0x38: this.utfEnabled = true; break;
3619 case 6 /* ESfunckey */:
3620 this.isEsc = 0 /* ESnormal */; break;
3621 case 7 /* EShash */:
3622 this.isEsc = 0 /* ESnormal */;
3623 /*8*/ if (ch == 0x38) {
3624 // Screen alignment test not implemented
3627 case 8 /* ESsetG0 */:
3628 case 9 /* ESsetG1 */:
3629 case 10 /* ESsetG2 */:
3630 case 11 /* ESsetG3 */:
3631 var g = this.isEsc - 8 /* ESsetG0 */;
3632 this.isEsc = 0 /* ESnormal */;
3634 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3636 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3637 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3638 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3641 if (this.useGMap == g) {
3642 this.translate = this.GMap[g];
3645 case 17 /* ESstatus */:
3647 if (this.statusString && this.statusString.charAt(0) == ';') {
3648 this.statusString = this.statusString.substr(1);
3651 window.status = this.statusString;
3654 this.isEsc = 0 /* ESnormal */;
3656 this.statusString += String.fromCharCode(ch);
3659 case 18 /* ESss2 */:
3660 case 19 /* ESss3 */:
3662 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3663 [this.toggleMeta ? (ch | 0x80) : ch];
3664 if ((ch & 0xFF00) == 0xF000) {
3666 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3667 this.isEsc = 0 /* ESnormal */; break;
3670 this.lastCharacter = String.fromCharCode(ch);
3671 lineBuf += this.lastCharacter;
3672 this.isEsc = 0 /* ESnormal */; break;
3674 this.isEsc = 0 /* ESnormal */; break;
3681 VT100.prototype.renderString = function(s, showCursor) {
3682 if (this.printing) {
3683 this.sendToPrinter(s);
3690 // We try to minimize the number of DOM operations by coalescing individual
3691 // characters into strings. This is a significant performance improvement.
3692 var incX = s.length;
3693 if (incX > this.terminalWidth - this.cursorX) {
3694 incX = this.terminalWidth - this.cursorX;
3698 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3701 // Minimize the number of calls to putString(), by avoiding a direct
3702 // call to this.showCursor()
3703 this.cursor.style.visibility = '';
3705 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3708 VT100.prototype.vt100 = function(s) {
3709 this.cursorNeedsShowing = this.hideCursor();
3710 this.respondString = '';
3712 for (var i = 0; i < s.length; i++) {
3713 var ch = s.charCodeAt(i);
3714 if (this.utfEnabled) {
3715 // Decode UTF8 encoded character
3717 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3718 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3719 if (--this.utfCount <= 0) {
3720 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3729 if ((ch & 0xE0) == 0xC0) {
3731 this.utfChar = ch & 0x1F;
3732 } else if ((ch & 0xF0) == 0xE0) {
3734 this.utfChar = ch & 0x0F;
3735 } else if ((ch & 0xF8) == 0xF0) {
3737 this.utfChar = ch & 0x07;
3738 } else if ((ch & 0xFC) == 0xF8) {
3740 this.utfChar = ch & 0x03;
3741 } else if ((ch & 0xFE) == 0xFC) {
3743 this.utfChar = ch & 0x01;
3753 var isNormalCharacter =
3754 (ch >= 32 && ch <= 127 || ch >= 160 ||
3755 this.utfEnabled && ch >= 128 ||
3756 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3757 (ch != 0x7F || this.dispCtrl);
3759 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3761 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3763 if ((ch & 0xFF00) == 0xF000) {
3765 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3768 if (!this.printing) {
3769 if (this.needWrap || this.insertMode) {
3771 this.renderString(lineBuf);
3775 if (this.needWrap) {
3776 this.cr(); this.lf();
3778 if (this.insertMode) {
3779 this.scrollRegion(this.cursorX, this.cursorY,
3780 this.terminalWidth - this.cursorX - 1, 1,
3781 1, 0, this.color, this.style);
3784 this.lastCharacter = String.fromCharCode(ch);
3785 lineBuf += this.lastCharacter;
3786 if (!this.printing &&
3787 this.cursorX + lineBuf.length >= this.terminalWidth) {
3788 this.needWrap = this.autoWrapMode;
3792 this.renderString(lineBuf);
3795 var expand = this.doControl(ch);
3796 if (expand.length) {
3797 var r = this.respondString;
3798 this.respondString= r + this.vt100(expand);
3803 this.renderString(lineBuf, this.cursorNeedsShowing);
3804 } else if (this.cursorNeedsShowing) {
3807 return this.respondString;
3810 VT100.prototype.Latin1Map = [
3811 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3812 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3813 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3814 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3815 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3816 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3817 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3818 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3819 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3820 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3821 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3822 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3823 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3824 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3825 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3826 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3827 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3828 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3829 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3830 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3831 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3832 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3833 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3834 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3835 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3836 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3837 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3838 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3839 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3840 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3841 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3842 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3845 VT100.prototype.VT100GraphicsMap = [
3846 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3847 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3848 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3849 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3850 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3851 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3852 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3853 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3854 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3855 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3856 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3857 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3858 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3859 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3860 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3861 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3862 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3863 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3864 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3865 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3866 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3867 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3868 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3869 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3870 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3871 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3872 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3873 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3874 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3875 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3876 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3877 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3880 VT100.prototype.CodePage437Map = [
3881 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3882 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3883 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3884 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3885 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3886 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3887 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3888 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3889 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3890 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3891 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3892 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3893 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3894 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3895 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3896 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3897 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3898 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3899 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3900 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3901 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3902 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3903 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3904 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3905 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3906 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3907 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3908 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3909 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3910 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3911 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3912 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3915 VT100.prototype.DirectToFontMap = [
3916 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3917 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3918 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3919 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3920 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3921 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3922 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3923 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3924 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3925 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3926 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3927 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3928 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3929 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3930 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3931 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3932 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3933 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3934 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3935 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3936 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3937 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3938 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3939 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3940 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3941 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3942 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3943 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3944 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3945 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3946 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3947 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3950 VT100.prototype.ctrlAction = [
3951 true, false, false, false, false, false, false, true,
3952 true, true, true, true, true, true, true, true,
3953 false, false, false, false, false, false, false, false,
3954 true, false, true, true, false, false, false, false
3957 VT100.prototype.ctrlAlways = [
3958 true, false, false, false, false, false, false, false,
3959 true, false, true, false, true, true, true, true,
3960 false, false, false, false, false, false, false, false,
3961 false, false, false, true, false, false, false, false