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 if (this.visualBell) {
290 this.signature = Math.floor(16807*this.signature + 1) %
293 if (typeof userCSSList != 'undefined') {
294 for (var i = 0; i < userCSSList.length; ++i) {
295 var label = userCSSList[i][0];
296 for (var j = 0; j < label.length; ++j) {
297 this.signature = Math.floor(16807*this.signature+
298 label.charCodeAt(j)) %
301 if (userCSSList[i][1]) {
302 this.signature = Math.floor(16807*this.signature + 1) %
308 var key = 'shellInABox=' + this.signature + ':';
309 var settings = document.cookie.indexOf(key);
311 settings = document.cookie.substr(settings + key.length).
312 replace(/([0-1]*).*/, "$1");
313 if (settings.length == 3 + (typeof userCSSList == 'undefined' ?
314 0 : userCSSList.length)) {
315 this.utfPreferred = settings.charAt(0) != '0';
316 this.visualBell = settings.charAt(1) != '0';
317 this.autoprint = settings.charAt(2) != '0';
318 if (typeof userCSSList != 'undefined') {
319 for (var i = 0; i < userCSSList.length; ++i) {
320 userCSSList[i][2] = settings.charAt(i + 3) != '0';
325 this.utfEnabled = this.utfPreferred;
328 VT100.prototype.storeUserSettings = function() {
329 var settings = 'shellInABox=' + this.signature + ':' +
330 (this.utfEnabled ? '1' : '0') +
331 (this.visualBell ? '1' : '0') +
332 (this.autoprint ? '1' : '0');
333 if (typeof userCSSList != 'undefined') {
334 for (var i = 0; i < userCSSList.length; ++i) {
335 settings += userCSSList[i][2] ? '1' : '0';
339 d.setDate(d.getDate() + 3653);
340 document.cookie = settings + ';expires=' + d.toGMTString();
343 VT100.prototype.initializeUserCSSStyles = function() {
344 this.usercssActions = [];
345 if (typeof userCSSList != 'undefined') {
348 var wasSingleSel = 1;
349 var beginOfGroup = 0;
350 for (var i = 0; i <= userCSSList.length; ++i) {
351 if (i < userCSSList.length) {
352 var label = userCSSList[i][0];
353 var newGroup = userCSSList[i][1];
354 var enabled = userCSSList[i][2];
356 // Add user style sheet to document
357 var style = document.createElement('link');
358 var id = document.createAttribute('id');
359 id.nodeValue = 'usercss-' + i;
360 style.setAttributeNode(id);
361 var rel = document.createAttribute('rel');
362 rel.nodeValue = 'stylesheet';
363 style.setAttributeNode(rel);
364 var href = document.createAttribute('href');
365 href.nodeValue = 'usercss-' + i + '.css';
366 style.setAttributeNode(href);
367 var type = document.createAttribute('type');
368 type.nodeValue = 'text/css';
369 style.setAttributeNode(type);
370 document.getElementsByTagName('head')[0].appendChild(style);
371 style.disabled = !enabled;
375 if (newGroup || i == userCSSList.length) {
376 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
377 // The last group had multiple entries that are mutually exclusive;
378 // or the previous to last group did. In either case, we need to
379 // append a "<hr />" before we can add the last group to the menu.
382 wasSingleSel = i - beginOfGroup < 1;
386 for (var j = beginOfGroup; j < i; ++j) {
387 this.usercssActions[this.usercssActions.length] =
388 function(vt100, current, begin, count) {
390 // Deselect all other entries in the group, then either select
391 // (for multiple entries in group) or toggle (for on/off entry)
392 // the current entry.
394 var entry = vt100.getChildById(vt100.menu,
398 for (var c = count; c > 0; ++j) {
399 if (entry.tagName == 'LI') {
402 var label = vt100.usercss.childNodes[j];
404 // Restore label to just the text content
405 if (typeof label.textContent == 'undefined') {
406 var s = label.innerText;
407 label.innerHTML = '';
408 label.appendChild(document.createTextNode(s));
410 label.textContent= label.textContent;
413 // User style sheets are number sequentially
414 var sheet = document.getElementById(
418 sheet.disabled = !sheet.disabled;
420 sheet.disabled = false;
422 if (!sheet.disabled) {
423 label.innerHTML= '<img src="enabled.gif" />' +
427 sheet.disabled = true;
429 userCSSList[i][2] = !sheet.disabled;
432 entry = entry.nextSibling;
435 }(this, j, beginOfGroup, i - beginOfGroup);
438 if (i == userCSSList.length) {
444 // Collect all entries in a group, before attaching them to the menu.
445 // This is necessary as we don't know whether this is a group of
446 // mutually exclusive options (which should be separated by "<hr />" on
447 // both ends), or whether this is a on/off toggle, which can be grouped
448 // together with other on/off options.
450 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
454 this.usercss.innerHTML = menu;
458 VT100.prototype.initializeElements = function(container) {
459 // If the necessary objects have not already been defined in the HTML
460 // page, create them now.
462 this.container = container;
463 } else if (!(this.container = document.getElementById('vt100'))) {
464 this.container = document.createElement('div');
465 this.container.id = 'vt100';
466 document.body.appendChild(this.container);
469 if (!this.getChildById(this.container, 'reconnect') ||
470 !this.getChildById(this.container, 'menu') ||
471 !this.getChildById(this.container, 'scrollable') ||
472 !this.getChildById(this.container, 'console') ||
473 !this.getChildById(this.container, 'alt_console') ||
474 !this.getChildById(this.container, 'ieprobe') ||
475 !this.getChildById(this.container, 'padding') ||
476 !this.getChildById(this.container, 'cursor') ||
477 !this.getChildById(this.container, 'lineheight') ||
478 !this.getChildById(this.container, 'usercss') ||
479 !this.getChildById(this.container, 'space') ||
480 !this.getChildById(this.container, 'input') ||
481 !this.getChildById(this.container, 'cliphelper')) {
482 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
483 // we might get a pointless warning that a suitable plugin is not yet
484 // installed. If in doubt, we'd rather just stay silent.
487 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
489 embed = typeof suppressAllAudio != 'undefined' &&
490 suppressAllAudio ? "" :
491 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
494 'autostart="false" ' +
496 'enablejavascript="true" ' +
497 'type="audio/x-wav" ' +
500 'style="position:absolute;left:-1000px;top:-1000px" />';
505 this.container.innerHTML =
506 '<div id="reconnect" style="visibility: hidden">' +
507 '<input type="button" value="Connect" ' +
508 'onsubmit="return false" />' +
510 '<div id="cursize" style="visibility: hidden">' +
512 '<div id="menu"></div>' +
513 '<div id="scrollable">' +
514 '<pre id="lineheight"> </pre>' +
515 '<pre id="console">' +
517 '<div id="ieprobe"><span> </span></div>' +
519 '<pre id="alt_console" style="display: none"></pre>' +
520 '<div id="padding"></div>' +
521 '<pre id="cursor"> </pre>' +
523 '<div class="hidden">' +
524 '<div id="usercss"></div>' +
525 '<pre><div><span id="space"></span></div></pre>' +
526 '<input type="textfield" id="input" />' +
527 '<input type="textfield" id="cliphelper" />' +
528 (typeof suppressAllAudio != 'undefined' &&
529 suppressAllAudio ? "" :
530 embed + '<bgsound id="beep_bgsound" loop=1 />') +
534 // Find the object used for playing the "beep" sound, if any.
535 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
536 this.beeper = undefined;
538 this.beeper = this.getChildById(this.container,
540 if (!this.beeper || !this.beeper.Play) {
541 this.beeper = this.getChildById(this.container,
543 if (!this.beeper || typeof this.beeper.src == 'undefined') {
544 this.beeper = undefined;
549 // Initialize the variables for finding the text console and the
551 this.reconnectBtn = this.getChildById(this.container,'reconnect');
552 this.curSizeBox = this.getChildById(this.container, 'cursize');
553 this.menu = this.getChildById(this.container, 'menu');
554 this.scrollable = this.getChildById(this.container,
556 this.lineheight = this.getChildById(this.container,
559 [ this.getChildById(this.container, 'console'),
560 this.getChildById(this.container, 'alt_console') ];
561 var ieProbe = this.getChildById(this.container, 'ieprobe');
562 this.padding = this.getChildById(this.container, 'padding');
563 this.cursor = this.getChildById(this.container, 'cursor');
564 this.usercss = this.getChildById(this.container, 'usercss');
565 this.space = this.getChildById(this.container, 'space');
566 this.input = this.getChildById(this.container, 'input');
567 this.cliphelper = this.getChildById(this.container,
570 // Add any user selectable style sheets to the menu
571 this.initializeUserCSSStyles();
573 // Remember the dimensions of a standard character glyph. We would
574 // expect that we could just check cursor.clientWidth/Height at any time,
575 // but it turns out that browsers sometimes invalidate these values
576 // (e.g. while displaying a print preview screen).
577 this.cursorWidth = this.cursor.clientWidth;
578 this.cursorHeight = this.lineheight.clientHeight;
580 // IE has a slightly different boxing model, that we need to compensate for
581 this.isIE = ieProbe.offsetTop > 1;
583 this.console.innerHTML = '';
585 // Determine if the terminal window is positioned at the beginning of the
586 // page, or if it is embedded somewhere else in the page. For full-screen
587 // terminals, automatically resize whenever the browser window changes.
588 var marginTop = parseInt(this.getCurrentComputedStyle(
589 document.body, 'marginTop'));
590 var marginLeft = parseInt(this.getCurrentComputedStyle(
591 document.body, 'marginLeft'));
592 var marginRight = parseInt(this.getCurrentComputedStyle(
593 document.body, 'marginRight'));
594 var x = this.container.offsetLeft;
595 var y = this.container.offsetTop;
596 for (var parent = this.container; parent = parent.offsetParent; ) {
597 x += parent.offsetLeft;
598 y += parent.offsetTop;
600 this.isEmbedded = marginTop != y ||
602 (window.innerWidth ||
603 document.documentElement.clientWidth ||
604 document.body.clientWidth) -
605 marginRight != x + this.container.offsetWidth;
606 if (!this.isEmbedded) {
607 // Some browsers generate resize events when the terminal is first
608 // shown. Disable showing the size indicator until a little bit after
609 // the terminal has been rendered the first time.
610 this.indicateSize = false;
611 setTimeout(function(vt100) {
613 vt100.indicateSize = true;
616 this.addListener(window, 'resize',
619 vt100.hideContextMenu();
621 vt100.showCurrentSize();
625 // Hide extra scrollbars attached to window
626 document.body.style.margin = '0px';
627 try { document.body.style.overflow ='hidden'; } catch (e) { }
628 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
632 this.hideContextMenu();
634 // Add listener to reconnect button
635 this.addListener(this.reconnectBtn.firstChild, 'click',
638 var rc = vt100.reconnect();
644 // Add input listeners
645 this.addListener(this.input, 'blur',
647 return function() { vt100.blurCursor(); } }(this));
648 this.addListener(this.input, 'focus',
650 return function() { vt100.focusCursor(); } }(this));
651 this.addListener(this.input, 'keydown',
654 if (!e) e = window.event;
655 return vt100.keyDown(e); } }(this));
656 this.addListener(this.input, 'keypress',
659 if (!e) e = window.event;
660 return vt100.keyPressed(e); } }(this));
661 this.addListener(this.input, 'keyup',
664 if (!e) e = window.event;
665 return vt100.keyUp(e); } }(this));
667 // Attach listeners that move the focus to the <input> field. This way we
668 // can make sure that we can receive keyboard input.
669 var mouseEvent = function(vt100, type) {
671 if (!e) e = window.event;
672 return vt100.mouseEvent(e, type);
675 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
676 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
677 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
679 // Initialize the blank terminal window.
680 this.currentScreen = 0;
683 this.numScrollbackLines = 0;
685 this.bottom = 0x7FFFFFFF;
692 VT100.prototype.getChildById = function(parent, id) {
693 var nodeList = parent.all || parent.getElementsByTagName('*');
694 if (typeof nodeList.namedItem == 'undefined') {
695 for (var i = 0; i < nodeList.length; i++) {
696 if (nodeList[i].id == id) {
702 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
703 return elem ? elem[0] || elem : null;
707 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
708 if (typeof elem.currentStyle != 'undefined') {
709 return elem.currentStyle[style];
711 return document.defaultView.getComputedStyle(elem, null)[style];
715 VT100.prototype.reconnect = function() {
719 VT100.prototype.showReconnect = function(state) {
721 this.reconnectBtn.style.visibility = '';
723 this.reconnectBtn.style.visibility = 'hidden';
727 VT100.prototype.repairElements = function(console) {
728 for (var line = console.firstChild; line; line = line.nextSibling) {
729 if (!line.clientHeight) {
730 var newLine = document.createElement(line.tagName);
731 newLine.style.cssText = line.style.cssText;
732 newLine.className = line.className;
733 if (line.tagName == 'DIV') {
734 for (var span = line.firstChild; span; span = span.nextSibling) {
735 var newSpan = document.createElement(span.tagName);
736 newSpan.style.cssText = span.style.cssText;
737 newSpan.style.className = span.style.className;
738 this.setTextContent(newSpan, this.getTextContent(span));
739 newLine.appendChild(newSpan);
742 this.setTextContent(newLine, this.getTextContent(line));
744 line.parentNode.replaceChild(newLine, line);
750 VT100.prototype.resized = function(w, h) {
753 VT100.prototype.resizer = function() {
754 // The cursor can get corrupted if the print-preview is displayed in Firefox.
755 // Recreating it, will repair it.
756 var newCursor = document.createElement('pre');
757 this.setTextContent(newCursor, ' ');
758 newCursor.id = 'cursor';
759 newCursor.style.cssText = this.cursor.style.cssText;
760 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
761 if (!newCursor.clientHeight) {
762 // Things are broken right now. This is probably because we are
763 // displaying the print-preview. Just don't change any of our settings
764 // until the print dialog is closed again.
765 newCursor.parentNode.removeChild(newCursor);
768 // Swap the old broken cursor for the newly created one.
769 this.cursor.parentNode.removeChild(this.cursor);
770 this.cursor = newCursor;
773 // Really horrible things happen if the contents of the terminal changes
774 // while the print-preview is showing. We get HTML elements that show up
775 // in the DOM, but that do not take up any space. Find these elements and
777 this.repairElements(this.console[0]);
778 this.repairElements(this.console[1]);
780 // Lock the cursor size to the size of a normal character. This helps with
781 // characters that are taller/shorter than normal. Unfortunately, we will
782 // still get confused if somebody enters a character that is wider/narrower
783 // than normal. This can happen if the browser tries to substitute a
784 // characters from a different font.
785 this.cursor.style.width = this.cursorWidth + 'px';
786 this.cursor.style.height = this.cursorHeight + 'px';
788 // Adjust height for one pixel padding of the #vt100 element.
789 // The latter is necessary to properly display the inactive cursor.
790 var console = this.console[this.currentScreen];
791 var height = (this.isEmbedded ? this.container.clientHeight
792 : (window.innerHeight ||
793 document.documentElement.clientHeight ||
794 document.body.clientHeight))-1;
795 var partial = height % this.cursorHeight;
796 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
797 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
798 var oldTerminalHeight = this.terminalHeight;
802 // Clip the cursor to the visible screen.
803 var cx = this.cursorX;
804 var cy = this.cursorY + this.numScrollbackLines;
806 // The alternate screen never keeps a scroll back buffer.
807 this.updateNumScrollbackLines();
808 while (this.currentScreen && this.numScrollbackLines > 0) {
809 console.removeChild(console.firstChild);
810 this.numScrollbackLines--;
812 cy -= this.numScrollbackLines;
815 } else if (cx > this.terminalWidth) {
816 cx = this.terminalWidth - 1;
823 } else if (cy > this.terminalHeight) {
824 cy = this.terminalHeight - 1;
830 // Clip the scroll region to the visible screen.
831 if (this.bottom > this.terminalHeight ||
832 this.bottom == oldTerminalHeight) {
833 this.bottom = this.terminalHeight;
835 if (this.top >= this.bottom) {
836 this.top = this.bottom-1;
842 // Truncate lines, if necessary. Explicitly reposition cursor (this is
843 // particularly important after changing the screen number), and reset
844 // the scroll region to the default.
845 this.truncateLines(this.terminalWidth);
846 this.putString(cx, cy, '', undefined);
847 this.scrollable.scrollTop = this.numScrollbackLines *
848 this.cursorHeight + 1;
850 // Update classNames for lines in the scrollback buffer
851 var line = console.firstChild;
852 for (var i = 0; i < this.numScrollbackLines; i++) {
853 line.className = 'scrollback';
854 line = line.nextSibling;
858 line = line.nextSibling;
861 // Reposition the reconnect button
862 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
864 this.reconnectBtn.clientWidth)/2 + 'px';
865 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
866 this.reconnectBtn.clientHeight)/2 + 'px';
868 // Send notification that the window size has been changed
869 this.resized(this.terminalWidth, this.terminalHeight);
872 VT100.prototype.showCurrentSize = function() {
873 if (!this.indicateSize) {
876 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
878 this.curSizeBox.style.left =
879 (this.terminalWidth*this.cursorWidth/
881 this.curSizeBox.clientWidth)/2 + 'px';
882 this.curSizeBox.style.top =
883 (this.terminalHeight*this.cursorHeight -
884 this.curSizeBox.clientHeight)/2 + 'px';
885 this.curSizeBox.style.visibility = '';
886 if (this.curSizeTimeout) {
887 clearTimeout(this.curSizeTimeout);
890 // Only show the terminal size for a short amount of time after resizing.
891 // Then hide this information, again. Some browsers generate resize events
892 // throughout the entire resize operation. This is nice, and we will show
893 // the terminal size while the user is dragging the window borders.
894 // Other browsers only generate a single event when the user releases the
895 // mouse. In those cases, we can only show the terminal size once at the
896 // end of the resize operation.
897 this.curSizeTimeout = setTimeout(function(vt100) {
899 vt100.curSizeTimeout = null;
900 vt100.curSizeBox.style.visibility = 'hidden';
905 VT100.prototype.selection = function() {
907 return '' + (window.getSelection && window.getSelection() ||
908 document.selection && document.selection.type == 'Text' &&
909 document.selection.createRange().text || '');
915 VT100.prototype.cancelEvent = function(event) {
917 // For non-IE browsers
918 event.stopPropagation();
919 event.preventDefault();
924 event.cancelBubble = true;
925 event.returnValue = false;
933 VT100.prototype.mouseEvent = function(event, type) {
934 // If any text is currently selected, do not move the focus as that would
935 // invalidate the selection.
936 var selection = this.selection();
937 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
941 // Compute mouse position in characters.
942 var offsetX = this.container.offsetLeft;
943 var offsetY = this.container.offsetTop;
944 for (var e = this.container; e = e.offsetParent; ) {
945 offsetX += e.offsetLeft;
946 offsetY += e.offsetTop;
948 var x = (event.clientX - offsetX) / this.cursorWidth;
949 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
950 this.cursorHeight - this.numScrollbackLines;
952 if (x >= this.terminalWidth) {
953 x = this.terminalWidth - 1;
960 if (y >= this.terminalHeight) {
961 y = this.terminalHeight - 1;
969 // Compute button number and modifier keys.
970 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
971 typeof event.pageX != 'undefined' ? event.button :
972 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
973 if (button != undefined) {
974 if (event.shiftKey) {
977 if (event.altKey || event.metaKey) {
985 // Report mouse events if they happen inside of the current screen and
986 // with the SHIFT key unpressed. Both of these restrictions do not apply
987 // for button releases, as we always want to report those.
988 if (this.mouseReporting && !selection.length &&
989 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
990 if (inside || type != 0 /* MOUSE_DOWN */) {
991 if (button != undefined) {
992 var report = '\u001B[M' + String.fromCharCode(button + 32) +
993 String.fromCharCode(x + 33) +
994 String.fromCharCode(y + 33);
995 if (type != 2 /* MOUSE_CLICK */) {
996 this.keysPressed(report);
999 // If we reported the event, stop propagating it (not sure, if this
1000 // actually works on most browsers; blocking the global "oncontextmenu"
1001 // even is still necessary).
1002 return this.cancelEvent(event);
1007 // Bring up context menu.
1008 if (button == 2 && !event.shiftKey) {
1009 if (type == 0 /* MOUSE_DOWN */) {
1010 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
1012 return this.cancelEvent(event);
1015 if (this.mouseReporting) {
1017 event.shiftKey = false;
1025 VT100.prototype.replaceChar = function(s, ch, repl) {
1026 for (var i = -1;;) {
1027 i = s.indexOf(ch, i + 1);
1031 s = s.substr(0, i) + repl + s.substr(i + 1);
1036 VT100.prototype.htmlEscape = function(s) {
1037 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1038 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1041 VT100.prototype.getTextContent = function(elem) {
1042 return elem.textContent ||
1043 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1046 VT100.prototype.setTextContent = function(elem, s) {
1047 // Check if we find any URLs in the text. If so, automatically convert them
1049 if (this.urlRE && this.urlRE.test(s)) {
1053 if (RegExp.leftContext != null) {
1054 inner += this.htmlEscape(RegExp.leftContext);
1055 consumed += RegExp.leftContext.length;
1057 var url = this.htmlEscape(RegExp.lastMatch);
1060 // If no protocol was specified, try to guess a reasonable one.
1061 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1062 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1063 var slash = url.indexOf('/');
1064 var at = url.indexOf('@');
1065 var question = url.indexOf('?');
1067 (at < question || question < 0) &&
1068 (slash < 0 || (question > 0 && slash > question))) {
1069 fullUrl = 'mailto:' + url;
1071 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1076 inner += '<a target="vt100Link" href="' + fullUrl +
1077 '">' + url + '</a>';
1078 consumed += RegExp.lastMatch.length;
1079 s = s.substr(consumed);
1080 if (!this.urlRE.test(s)) {
1081 if (RegExp.rightContext != null) {
1082 inner += this.htmlEscape(RegExp.rightContext);
1087 elem.innerHTML = inner;
1091 // Updating the content of an element is an expensive operation. It actually
1092 // pays off to first check whether the element is still unchanged.
1093 if (typeof elem.textContent == 'undefined') {
1094 if (elem.innerText != s) {
1098 // Very old versions of IE do not allow setting innerText. Instead,
1099 // remove all children, by setting innerHTML and then set the text
1100 // using DOM methods.
1101 elem.innerHTML = '';
1102 elem.appendChild(document.createTextNode(
1103 this.replaceChar(s, ' ', '\u00A0')));
1107 if (elem.textContent != s) {
1108 elem.textContent = s;
1113 VT100.prototype.insertBlankLine = function(y, color, style) {
1114 // Insert a blank line a position y. This method ignores the scrollback
1115 // buffer. The caller has to add the length of the scrollback buffer to
1116 // the position, if necessary.
1117 // If the position is larger than the number of current lines, this
1118 // method just adds a new line right after the last existing one. It does
1119 // not add any missing lines in between. It is the caller's responsibility
1122 color = 'ansi0 bgAnsi15';
1128 if (color != 'ansi0 bgAnsi15' && !style) {
1129 line = document.createElement('pre');
1130 this.setTextContent(line, '\n');
1132 line = document.createElement('div');
1133 var span = document.createElement('span');
1134 span.style.cssText = style;
1135 span.style.className = color;
1136 this.setTextContent(span, this.spaces(this.terminalWidth));
1137 line.appendChild(span);
1139 line.style.height = this.cursorHeight + 'px';
1140 var console = this.console[this.currentScreen];
1141 if (console.childNodes.length > y) {
1142 console.insertBefore(line, console.childNodes[y]);
1144 console.appendChild(line);
1148 VT100.prototype.updateWidth = function() {
1149 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1150 this.cursorWidth*this.scale);
1151 return this.terminalWidth;
1154 VT100.prototype.updateHeight = function() {
1155 // We want to be able to display either a terminal window that fills the
1156 // entire browser window, or a terminal window that is contained in a
1157 // <div> which is embededded somewhere in the web page.
1158 if (this.isEmbedded) {
1159 // Embedded terminal. Use size of the containing <div> (id="vt100").
1160 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1163 // Use the full browser window.
1164 this.terminalHeight = Math.floor(((window.innerHeight ||
1165 document.documentElement.clientHeight ||
1166 document.body.clientHeight)-1)/
1169 return this.terminalHeight;
1172 VT100.prototype.updateNumScrollbackLines = function() {
1173 var scrollback = Math.floor(
1174 this.console[this.currentScreen].offsetHeight /
1175 this.cursorHeight) -
1176 this.terminalHeight;
1177 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1178 return this.numScrollbackLines;
1181 VT100.prototype.truncateLines = function(width) {
1185 for (var line = this.console[this.currentScreen].firstChild; line;
1186 line = line.nextSibling) {
1187 if (line.tagName == 'DIV') {
1190 // Traverse current line and truncate it once we saw "width" characters
1191 for (var span = line.firstChild; span;
1192 span = span.nextSibling) {
1193 var s = this.getTextContent(span);
1195 if (x + l > width) {
1196 this.setTextContent(span, s.substr(0, width - x));
1197 while (span.nextSibling) {
1198 line.removeChild(line.lastChild);
1204 // Prune white space from the end of the current line
1205 var span = line.lastChild;
1207 span.className == 'ansi0 bgAnsi15' &&
1208 !span.style.cssText.length) {
1209 // Scan backwards looking for first non-space character
1210 var s = this.getTextContent(span);
1211 for (var i = s.length; i--; ) {
1212 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1213 if (i+1 != s.length) {
1214 this.setTextContent(s.substr(0, i+1));
1222 span = span.previousSibling;
1224 // Remove blank <span>'s from end of line
1225 line.removeChild(sibling);
1227 // Remove entire line (i.e. <div>), if empty
1228 var blank = document.createElement('pre');
1229 blank.style.height = this.cursorHeight + 'px';
1230 this.setTextContent(blank, '\n');
1231 line.parentNode.replaceChild(blank, line);
1239 VT100.prototype.putString = function(x, y, text, color, style) {
1241 color = 'ansi0 bgAnsi15';
1246 var yIdx = y + this.numScrollbackLines;
1252 var console = this.console[this.currentScreen];
1253 if (!text.length && (yIdx >= console.childNodes.length ||
1254 console.childNodes[yIdx].tagName != 'DIV')) {
1255 // Positioning cursor to a blank location
1258 // Create missing blank lines at end of page
1259 while (console.childNodes.length <= yIdx) {
1260 // In order to simplify lookups, we want to make sure that each line
1261 // is represented by exactly one element (and possibly a whole bunch of
1263 // For non-blank lines, we can create a <div> containing one or more
1264 // <span>s. For blank lines, this fails as browsers tend to optimize them
1265 // away. But fortunately, a <pre> tag containing a newline character
1266 // appears to work for all browsers (a would also work, but then
1267 // copying from the browser window would insert superfluous spaces into
1269 this.insertBlankLine(yIdx);
1271 line = console.childNodes[yIdx];
1273 // If necessary, promote blank '\n' line to a <div> tag
1274 if (line.tagName != 'DIV') {
1275 var div = document.createElement('div');
1276 div.style.height = this.cursorHeight + 'px';
1277 div.innerHTML = '<span></span>';
1278 console.replaceChild(div, line);
1282 // Scan through list of <span>'s until we find the one where our text
1284 span = line.firstChild;
1286 while (span.nextSibling && xPos < x) {
1287 len = this.getTextContent(span).length;
1288 if (xPos + len > x) {
1292 span = span.nextSibling;
1296 // If current <span> is not long enough, pad with spaces or add new
1298 s = this.getTextContent(span);
1299 var oldColor = span.className;
1300 var oldStyle = span.style.cssText;
1301 if (xPos + s.length < x) {
1302 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1303 span = document.createElement('span');
1304 line.appendChild(span);
1305 span.className = 'ansi0 bgAnsi15';
1306 span.style.cssText = '';
1307 oldColor = 'ansi0 bgAnsi15';
1314 } while (xPos + s.length < x);
1317 // If styles do not match, create a new <span>
1318 var del = text.length - s.length + x - xPos;
1319 if (oldColor != color ||
1320 (oldStyle != style && (oldStyle || style))) {
1322 // Replacing text at beginning of existing <span>
1323 if (text.length >= s.length) {
1324 // New text is equal or longer than existing text
1327 // Insert new <span> before the current one, then remove leading
1328 // part of existing <span>, adjust style of new <span>, and finally
1330 sibling = document.createElement('span');
1331 line.insertBefore(sibling, span);
1332 this.setTextContent(span, s.substr(text.length));
1337 // Replacing text some way into the existing <span>
1338 var remainder = s.substr(x + text.length - xPos);
1339 this.setTextContent(span, s.substr(0, x - xPos));
1341 sibling = document.createElement('span');
1342 if (span.nextSibling) {
1343 line.insertBefore(sibling, span.nextSibling);
1345 if (remainder.length) {
1346 sibling = document.createElement('span');
1347 sibling.className = oldColor;
1348 sibling.style.cssText = oldStyle;
1349 this.setTextContent(sibling, remainder);
1350 line.insertBefore(sibling, span.nextSibling);
1353 line.appendChild(sibling);
1355 if (remainder.length) {
1356 sibling = document.createElement('span');
1357 sibling.className = oldColor;
1358 sibling.style.cssText = oldStyle;
1359 this.setTextContent(sibling, remainder);
1360 line.appendChild(sibling);
1365 span.className = color;
1366 span.style.cssText = style;
1368 // Overwrite (partial) <span> with new text
1369 s = s.substr(0, x - xPos) +
1371 s.substr(x + text.length - xPos);
1373 this.setTextContent(span, s);
1376 // Delete all subsequent <span>'s that have just been overwritten
1377 sibling = span.nextSibling;
1378 while (del > 0 && sibling) {
1379 s = this.getTextContent(sibling);
1382 line.removeChild(sibling);
1384 sibling = span.nextSibling;
1386 this.setTextContent(sibling, s.substr(del));
1391 // Merge <span> with next sibling, if styles are identical
1392 if (sibling && span.className == sibling.className &&
1393 span.style.cssText == sibling.style.cssText) {
1394 this.setTextContent(span,
1395 this.getTextContent(span) +
1396 this.getTextContent(sibling));
1397 line.removeChild(sibling);
1403 this.cursorX = x + text.length;
1404 if (this.cursorX >= this.terminalWidth) {
1405 this.cursorX = this.terminalWidth - 1;
1406 if (this.cursorX < 0) {
1412 if (!this.cursor.style.visibility) {
1413 var idx = this.cursorX - xPos;
1415 // If we are in a non-empty line, take the cursor Y position from the
1416 // other elements in this line. If dealing with broken, non-proportional
1417 // fonts, this is likely to yield better results.
1418 pixelY = span.offsetTop +
1419 span.offsetParent.offsetTop;
1420 s = this.getTextContent(span);
1421 var nxtIdx = idx - s.length;
1423 this.setTextContent(this.cursor, s.charAt(idx));
1424 pixelX = span.offsetLeft +
1425 idx*span.offsetWidth / s.length;
1428 pixelX = span.offsetLeft + span.offsetWidth;
1430 if (span.nextSibling) {
1431 s = this.getTextContent(span.nextSibling);
1432 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1434 pixelX = span.nextSibling.offsetLeft +
1435 nxtIdx*span.offsetWidth / s.length;
1438 this.setTextContent(this.cursor, ' ');
1442 this.setTextContent(this.cursor, ' ');
1446 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
1449 this.setTextContent(this.space, this.spaces(this.cursorX));
1450 this.cursor.style.left = (this.space.offsetWidth +
1451 console.offsetLeft)/this.scale + 'px';
1453 this.cursorY = yIdx - this.numScrollbackLines;
1455 this.cursor.style.top = pixelY + 'px';
1457 this.cursor.style.top = yIdx*this.cursorHeight +
1458 console.offsetTop + 'px';
1462 // Merge <span> with previous sibling, if styles are identical
1463 if ((sibling = span.previousSibling) &&
1464 span.className == sibling.className &&
1465 span.style.cssText == sibling.style.cssText) {
1466 this.setTextContent(span,
1467 this.getTextContent(sibling) +
1468 this.getTextContent(span));
1469 line.removeChild(sibling);
1472 // Prune white space from the end of the current line
1473 span = line.lastChild;
1475 span.className == 'ansi0 bgAnsi15' &&
1476 !span.style.cssText.length) {
1477 // Scan backwards looking for first non-space character
1478 s = this.getTextContent(span);
1479 for (var i = s.length; i--; ) {
1480 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1481 if (i+1 != s.length) {
1482 this.setTextContent(s.substr(0, i+1));
1490 span = span.previousSibling;
1492 // Remove blank <span>'s from end of line
1493 line.removeChild(sibling);
1495 // Remove entire line (i.e. <div>), if empty
1496 var blank = document.createElement('pre');
1497 blank.style.height = this.cursorHeight + 'px';
1498 this.setTextContent(blank, '\n');
1499 line.parentNode.replaceChild(blank, line);
1506 VT100.prototype.gotoXY = function(x, y) {
1507 if (x >= this.terminalWidth) {
1508 x = this.terminalWidth - 1;
1514 if (this.offsetMode) {
1519 maxY = this.terminalHeight;
1527 this.putString(x, y, '', undefined);
1528 this.needWrap = false;
1531 VT100.prototype.gotoXaY = function(x, y) {
1532 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1535 VT100.prototype.refreshInvertedState = function() {
1536 if (this.isInverted) {
1537 this.scrollable.className += ' inverted';
1539 this.scrollable.className = this.scrollable.className.
1540 replace(/ *inverted/, '');
1544 VT100.prototype.enableAlternateScreen = function(state) {
1545 // Don't do anything, if we are already on the desired screen
1546 if ((state ? 1 : 0) == this.currentScreen) {
1547 // Calling the resizer is not actually necessary. But it is a good way
1548 // of resetting state that might have gotten corrupted.
1553 // We save the full state of the normal screen, when we switch away from it.
1554 // But for the alternate screen, no saving is necessary. We always reset
1555 // it when we switch to it.
1560 // Display new screen, and initialize state (the resizer does that for us).
1561 this.currentScreen = state ? 1 : 0;
1562 this.console[1-this.currentScreen].style.display = 'none';
1563 this.console[this.currentScreen].style.display = '';
1565 // Select appropriate character pitch.
1566 var styles = [ 'transform',
1570 for (var i = 0; i < styles.length; ++i) {
1571 if (typeof this.console[0].style[styles[i]] != 'undefined') {
1573 // Upon enabling the alternate screen, we switch to 80 column mode. But
1574 // upon returning to the regular screen, we restore the mode that was
1575 // in effect previously.
1576 this.console[1].style[styles[i]] = '';
1579 this.console[this.currentScreen].style[styles[i]];
1580 this.cursor.style[styles[i]] = style;
1581 this.space.style[styles[i]] = style;
1582 this.scale = style == '' ? 1.0:1.65;
1583 if (styles[i] == 'filter') {
1584 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
1591 // If we switched to the alternate screen, reset it completely. Otherwise,
1592 // restore the saved state.
1595 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1597 this.restoreCursor();
1601 VT100.prototype.hideCursor = function() {
1602 var hidden = this.cursor.style.visibility == 'hidden';
1604 this.cursor.style.visibility = 'hidden';
1610 VT100.prototype.showCursor = function(x, y) {
1611 if (this.cursor.style.visibility) {
1612 this.cursor.style.visibility = '';
1613 this.putString(x == undefined ? this.cursorX : x,
1614 y == undefined ? this.cursorY : y,
1621 VT100.prototype.scrollBack = function() {
1622 var i = this.scrollable.scrollTop -
1623 this.scrollable.clientHeight;
1624 this.scrollable.scrollTop = i < 0 ? 0 : i;
1627 VT100.prototype.scrollFore = function() {
1628 var i = this.scrollable.scrollTop +
1629 this.scrollable.clientHeight;
1630 this.scrollable.scrollTop = i > this.numScrollbackLines *
1631 this.cursorHeight + 1
1632 ? this.numScrollbackLines *
1633 this.cursorHeight + 1
1637 VT100.prototype.spaces = function(i) {
1645 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
1650 if (w > this.terminalWidth) {
1651 w = this.terminalWidth;
1653 if ((w -= x) <= 0) {
1660 if (h > this.terminalHeight) {
1661 h = this.terminalHeight;
1663 if ((h -= y) <= 0) {
1667 // Special case the situation where we clear the entire screen, and we do
1668 // not have a scrollback buffer. In that case, we should just remove all
1670 if (!this.numScrollbackLines &&
1671 w == this.terminalWidth && h == this.terminalHeight &&
1672 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
1673 var console = this.console[this.currentScreen];
1674 while (console.lastChild) {
1675 console.removeChild(console.lastChild);
1677 this.putString(this.cursorX, this.cursorY, '', undefined);
1679 var hidden = this.hideCursor();
1680 var cx = this.cursorX;
1681 var cy = this.cursorY;
1682 var s = this.spaces(w);
1683 for (var i = y+h; i-- > y; ) {
1684 this.putString(x, i, s, color, style);
1686 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1690 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1692 var className = [ ];
1694 var console = this.console[this.currentScreen];
1695 if (sY >= console.childNodes.length) {
1696 text[0] = this.spaces(w);
1697 className[0] = undefined;
1698 style[0] = undefined;
1700 var line = console.childNodes[sY];
1701 if (line.tagName != 'DIV' || !line.childNodes.length) {
1702 text[0] = this.spaces(w);
1703 className[0] = undefined;
1704 style[0] = undefined;
1707 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1708 var s = this.getTextContent(span);
1711 var o = sX > x ? sX - x : 0;
1712 text[text.length] = s.substr(o, w);
1713 className[className.length] = span.className;
1714 style[style.length] = span.style.cssText;
1720 text[text.length] = this.spaces(w);
1721 className[className.length] = undefined;
1722 style[style.length] = undefined;
1726 var hidden = this.hideCursor();
1727 var cx = this.cursorX;
1728 var cy = this.cursorY;
1729 for (var i = 0; i < text.length; i++) {
1732 color = className[i];
1734 color = 'ansi0 bgAnsi15';
1736 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
1737 dX += text[i].length;
1739 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1742 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
1744 var left = incX < 0 ? -incX : 0;
1745 var right = incX > 0 ? incX : 0;
1746 var up = incY < 0 ? -incY : 0;
1747 var down = incY > 0 ? incY : 0;
1749 // Clip region against terminal size
1750 var dontScroll = null;
1755 if (w > this.terminalWidth - right) {
1756 w = this.terminalWidth - right;
1758 if ((w -= x) <= 0) {
1765 if (h > this.terminalHeight - down) {
1766 h = this.terminalHeight - down;
1772 if (style && style.indexOf('underline')) {
1773 // Different terminal emulators disagree on the attributes that
1774 // are used for scrolling. The consensus seems to be, never to
1775 // fill with underlined spaces. N.B. this is different from the
1776 // cases when the user blanks a region. User-initiated blanking
1777 // always fills with all of the current attributes.
1778 style = style.replace(/text-decoration:underline;/, '');
1781 // Compute current scroll position
1782 var scrollPos = this.numScrollbackLines -
1783 (this.scrollable.scrollTop-1) / this.cursorHeight;
1785 // Determine original cursor position. Hide cursor temporarily to avoid
1786 // visual artifacts.
1787 var hidden = this.hideCursor();
1788 var cx = this.cursorX;
1789 var cy = this.cursorY;
1790 var console = this.console[this.currentScreen];
1792 if (!incX && !x && w == this.terminalWidth) {
1793 // Scrolling entire lines
1796 if (!this.currentScreen && y == -incY &&
1797 h == this.terminalHeight + incY) {
1798 // Scrolling up with adding to the scrollback buffer. This is only
1799 // possible if there are at least as many lines in the console,
1800 // as the terminal is high
1801 while (console.childNodes.length < this.terminalHeight) {
1802 this.insertBlankLine(this.terminalHeight);
1805 // Add new lines at bottom in order to force scrolling
1806 for (var i = 0; i < y; i++) {
1807 this.insertBlankLine(console.childNodes.length, color, style);
1810 // Adjust the number of lines in the scrollback buffer by
1811 // removing excess entries.
1812 this.updateNumScrollbackLines();
1813 while (this.numScrollbackLines >
1814 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1815 console.removeChild(console.firstChild);
1816 this.numScrollbackLines--;
1819 // Mark lines in the scrollback buffer, so that they do not get
1821 for (var i = this.numScrollbackLines, j = -incY;
1822 i-- > 0 && j-- > 0; ) {
1823 console.childNodes[i].className = 'scrollback';
1826 // Scrolling up without adding to the scrollback buffer.
1829 console.childNodes.length >
1830 this.numScrollbackLines + y + incY; ) {
1831 console.removeChild(console.childNodes[
1832 this.numScrollbackLines + y + incY]);
1835 // If we used to have a scrollback buffer, then we must make sure
1836 // that we add back blank lines at the bottom of the terminal.
1837 // Similarly, if we are scrolling in the middle of the screen,
1838 // we must add blank lines to ensure that the bottom of the screen
1839 // does not move up.
1840 if (this.numScrollbackLines > 0 ||
1841 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1842 for (var i = -incY; i-- > 0; ) {
1843 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1852 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1853 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1855 for (var i = incY; i--; ) {
1856 this.insertBlankLine(this.numScrollbackLines + y, color, style);
1860 // Scrolling partial lines
1862 // Scrolling up or horizontally within a line
1863 for (var i = y + this.numScrollbackLines;
1864 i < y + this.numScrollbackLines + h;
1866 this.copyLineSegment(x + incX, i + incY, x, i, w);
1870 for (var i = y + this.numScrollbackLines + h;
1871 i-- > y + this.numScrollbackLines; ) {
1872 this.copyLineSegment(x + incX, i + incY, x, i, w);
1876 // Clear blank regions
1878 this.clearRegion(x, y, incX, h, color, style);
1879 } else if (incX < 0) {
1880 this.clearRegion(x + w + incX, y, -incX, h, color, style);
1883 this.clearRegion(x, y, w, incY, color, style);
1884 } else if (incY < 0) {
1885 this.clearRegion(x, y + h + incY, w, -incY, color, style);
1889 // Reset scroll position
1890 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1891 this.cursorHeight + 1;
1893 // Move cursor back to its original position
1894 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1898 VT100.prototype.copy = function(selection) {
1899 if (selection == undefined) {
1900 selection = this.selection();
1902 this.internalClipboard = undefined;
1903 if (selection.length) {
1906 this.cliphelper.value = selection;
1907 this.cliphelper.select();
1908 this.cliphelper.createTextRange().execCommand('copy');
1910 this.internalClipboard = selection;
1912 this.cliphelper.value = '';
1916 VT100.prototype.copyLast = function() {
1917 // Opening the context menu can remove the selection. We try to prevent this
1918 // from happening, but that is not possible for all browsers. So, instead,
1919 // we compute the selection before showing the menu.
1920 this.copy(this.lastSelection);
1923 VT100.prototype.pasteFnc = function() {
1924 var clipboard = undefined;
1925 if (this.internalClipboard != undefined) {
1926 clipboard = this.internalClipboard;
1929 this.cliphelper.value = '';
1930 this.cliphelper.createTextRange().execCommand('paste');
1931 clipboard = this.cliphelper.value;
1935 this.cliphelper.value = '';
1936 if (clipboard && this.menu.style.visibility == 'hidden') {
1938 this.keysPressed('' + clipboard);
1945 VT100.prototype.toggleUTF = function() {
1946 this.utfEnabled = !this.utfEnabled;
1948 // We always persist the last value that the user selected. Not necessarily
1949 // the last value that a random program requested.
1950 this.utfPreferred = this.utfEnabled;
1953 VT100.prototype.toggleBell = function() {
1954 this.visualBell = !this.visualBell;
1957 VT100.prototype.about = function() {
1958 alert("VT100 Terminal Emulator " + "2.10 (revision 207)" +
1959 "\nCopyright 2008-2010 by Markus Gutschke\n" +
1960 "For more information check http://shellinabox.com");
1963 VT100.prototype.hideContextMenu = function() {
1964 this.menu.style.visibility = 'hidden';
1965 this.menu.style.top = '-100px';
1966 this.menu.style.left = '-100px';
1967 this.menu.style.width = '0px';
1968 this.menu.style.height = '0px';
1971 VT100.prototype.extendContextMenu = function(entries, actions) {
1974 VT100.prototype.showContextMenu = function(x, y) {
1975 this.menu.innerHTML =
1976 '<table class="popup" ' +
1977 'cellpadding="0" cellspacing="0">' +
1979 '<ul id="menuentries">' +
1980 '<li id="beginclipboard">Copy</li>' +
1981 '<li id="endclipboard">Paste</li>' +
1983 '<li id="reset">Reset</li>' +
1985 '<li id="beginconfig">' +
1986 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
1988 '<li id="endconfig">' +
1989 (this.visualBell ? '<img src="enabled.gif" />' : '') +
1991 (this.usercss.firstChild ?
1992 '<hr id="beginusercss" />' +
1993 this.usercss.innerHTML +
1994 '<hr id="endusercss" />' :
1996 '<li id="about">About...</li>' +
2001 var popup = this.menu.firstChild;
2002 var menuentries = this.getChildById(popup, 'menuentries');
2004 // Determine menu entries that should be disabled
2005 this.lastSelection = this.selection();
2006 if (!this.lastSelection.length) {
2007 menuentries.firstChild.className
2010 var p = this.pasteFnc();
2012 menuentries.childNodes[1].className
2016 // Actions for default items
2017 var actions = [ this.copyLast, p, this.reset,
2018 this.toggleUTF, this.toggleBell ];
2020 // Actions for user CSS styles (if any)
2021 for (var i = 0; i < this.usercssActions.length; ++i) {
2022 actions[actions.length] = this.usercssActions[i];
2024 actions[actions.length] = this.about;
2026 // Allow subclasses to dynamically add entries to the context menu
2027 this.extendContextMenu(menuentries, actions);
2029 // Hook up event listeners
2030 for (var node = menuentries.firstChild, i = 0; node;
2031 node = node.nextSibling) {
2032 if (node.tagName == 'LI') {
2033 if (node.className != 'disabled') {
2034 this.addListener(node, 'mouseover',
2035 function(vt100, node) {
2037 node.className = 'hover';
2040 this.addListener(node, 'mouseout',
2041 function(vt100, node) {
2043 node.className = '';
2046 this.addListener(node, 'mousedown',
2047 function(vt100, action) {
2048 return function(event) {
2049 vt100.hideContextMenu();
2051 vt100.storeUserSettings();
2052 return vt100.cancelEvent(event || window.event);
2054 }(this, actions[i]));
2055 this.addListener(node, 'mouseup',
2057 return function(event) {
2058 return vt100.cancelEvent(event || window.event);
2061 this.addListener(node, 'mouseclick',
2063 return function(event) {
2064 return vt100.cancelEvent(event || window.event);
2072 // Position menu next to the mouse pointer
2073 if (x + popup.clientWidth > this.container.offsetWidth) {
2074 x = this.container.offsetWidth - popup.clientWidth;
2079 if (y + popup.clientHeight > this.container.offsetHeight) {
2080 y = this.container.offsetHeight-popup.clientHeight;
2085 popup.style.left = x + 'px';
2086 popup.style.top = y + 'px';
2088 // Block all other interactions with the terminal emulator
2089 this.menu.style.left = '0px';
2090 this.menu.style.top = '0px';
2091 this.menu.style.width = this.container.offsetWidth + 'px';
2092 this.menu.style.height = this.container.offsetHeight + 'px';
2093 this.addListener(this.menu, 'click', function(vt100) {
2095 vt100.hideContextMenu();
2100 this.menu.style.visibility = '';
2103 VT100.prototype.keysPressed = function(ch) {
2104 for (var i = 0; i < ch.length; i++) {
2105 var c = ch.charCodeAt(i);
2106 this.vt100(c >= 7 && c <= 15 ||
2107 c == 24 || c == 26 || c == 27 || c >= 32
2108 ? String.fromCharCode(c) : '<' + c + '>');
2112 VT100.prototype.applyModifiers = function(ch, event) {
2114 if (event.ctrlKey) {
2115 if (ch >= 32 && ch <= 127) {
2116 // For historic reasons, some control characters are treated specially
2118 case /* 3 */ 51: ch = 27; break;
2119 case /* 4 */ 52: ch = 28; break;
2120 case /* 5 */ 53: ch = 29; break;
2121 case /* 6 */ 54: ch = 30; break;
2122 case /* 7 */ 55: ch = 31; break;
2123 case /* 8 */ 56: ch = 127; break;
2124 case /* ? */ 63: ch = 127; break;
2125 default: ch &= 31; break;
2129 return String.fromCharCode(ch);
2135 VT100.prototype.handleKey = function(event) {
2136 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2137 // (event.shiftKey || event.ctrlKey || event.altKey ||
2138 // event.metaKey ? ', ' +
2139 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2140 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2143 if (typeof event.charCode != 'undefined') {
2144 // non-IE keypress events have a translated charCode value. Also, our
2145 // fake events generated when receiving keydown events include this data
2147 ch = event.charCode;
2148 key = event.keyCode;
2150 // When sending a keypress event, IE includes the translated character
2151 // code in the keyCode field.
2156 // Apply modifier keys (ctrl and shift)
2160 ch = this.applyModifiers(ch, event);
2162 // By this point, "ch" is either defined and contains the character code, or
2163 // it is undefined and "key" defines the code of a function key
2164 if (ch != undefined) {
2165 this.scrollable.scrollTop = this.numScrollbackLines *
2166 this.cursorHeight + 1;
2168 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2169 // Many programs have difficulties dealing with parametrized escape
2170 // sequences for function keys. Thus, if ALT is the only modifier
2171 // key, return Emacs-style keycodes for commonly used keys.
2173 case 33: /* Page Up */ ch = '\u001B<'; break;
2174 case 34: /* Page Down */ ch = '\u001B>'; break;
2175 case 37: /* Left */ ch = '\u001Bb'; break;
2176 case 38: /* Up */ ch = '\u001Bp'; break;
2177 case 39: /* Right */ ch = '\u001Bf'; break;
2178 case 40: /* Down */ ch = '\u001Bn'; break;
2179 case 46: /* Delete */ ch = '\u001Bd'; break;
2182 } else if (event.shiftKey && !event.ctrlKey &&
2183 !event.altKey && !event.metaKey) {
2185 case 33: /* Page Up */ this.scrollBack(); return;
2186 case 34: /* Page Down */ this.scrollFore(); return;
2190 if (ch == undefined) {
2192 case 8: /* Backspace */ ch = '\u007f'; break;
2193 case 9: /* Tab */ ch = '\u0009'; break;
2194 case 10: /* Return */ ch = '\u000A'; break;
2195 case 13: /* Enter */ ch = this.crLfMode ?
2196 '\r\n' : '\r'; break;
2197 case 16: /* Shift */ return;
2198 case 17: /* Ctrl */ return;
2199 case 18: /* Alt */ return;
2200 case 19: /* Break */ return;
2201 case 20: /* Caps Lock */ return;
2202 case 27: /* Escape */ ch = '\u001B'; break;
2203 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2204 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2205 case 35: /* End */ ch = '\u001BOF'; break;
2206 case 36: /* Home */ ch = '\u001BOH'; break;
2207 case 37: /* Left */ ch = this.cursorKeyMode ?
2208 '\u001BOD' : '\u001B[D'; break;
2209 case 38: /* Up */ ch = this.cursorKeyMode ?
2210 '\u001BOA' : '\u001B[A'; break;
2211 case 39: /* Right */ ch = this.cursorKeyMode ?
2212 '\u001BOC' : '\u001B[C'; break;
2213 case 40: /* Down */ ch = this.cursorKeyMode ?
2214 '\u001BOB' : '\u001B[B'; break;
2215 case 45: /* Insert */ ch = '\u001B[2~'; break;
2216 case 46: /* Delete */ ch = '\u001B[3~'; break;
2217 case 91: /* Left Window */ return;
2218 case 92: /* Right Window */ return;
2219 case 93: /* Select */ return;
2220 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2221 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2222 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2223 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2224 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2225 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2226 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2227 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2228 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2229 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2230 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2231 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2232 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2233 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2234 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2235 case 112: /* F1 */ ch = '\u001BOP'; break;
2236 case 113: /* F2 */ ch = '\u001BOQ'; break;
2237 case 114: /* F3 */ ch = '\u001BOR'; break;
2238 case 115: /* F4 */ ch = '\u001BOS'; break;
2239 case 116: /* F5 */ ch = '\u001B[15~'; break;
2240 case 117: /* F6 */ ch = '\u001B[17~'; break;
2241 case 118: /* F7 */ ch = '\u001B[18~'; break;
2242 case 119: /* F8 */ ch = '\u001B[19~'; break;
2243 case 120: /* F9 */ ch = '\u001B[20~'; break;
2244 case 121: /* F10 */ ch = '\u001B[21~'; break;
2245 case 122: /* F11 */ ch = '\u001B[23~'; break;
2246 case 123: /* F12 */ ch = '\u001B[24~'; break;
2247 case 144: /* Num Lock */ return;
2248 case 145: /* Scroll Lock */ return;
2249 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2250 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2251 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2252 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2253 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2254 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2255 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2256 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2257 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2258 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2259 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2262 this.scrollable.scrollTop = this.numScrollbackLines *
2263 this.cursorHeight + 1;
2267 // "ch" now contains the sequence of keycodes to send. But we might still
2268 // have to apply the effects of modifier keys.
2269 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2270 var start, digit, part1, part2;
2271 if ((start = ch.substr(0, 2)) == '\u001B[') {
2273 part1.length < ch.length &&
2274 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2275 part1 = ch.substr(0, part1.length + 1);
2277 part2 = ch.substr(part1.length);
2278 if (part1.length > 2) {
2281 } else if (start == '\u001BO') {
2283 part2 = ch.substr(2);
2285 if (part1 != undefined) {
2287 ((event.shiftKey ? 1 : 0) +
2288 (event.altKey|event.metaKey ? 2 : 0) +
2289 (event.ctrlKey ? 4 : 0)) +
2291 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2296 if (this.menu.style.visibility == 'hidden') {
2297 // this.vt100('R: c=');
2298 // for (var i = 0; i < ch.length; i++)
2299 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2300 // this.vt100('\r\n');
2301 this.keysPressed(ch);
2305 VT100.prototype.inspect = function(o, d) {
2306 if (d == undefined) {
2310 if (typeof o == 'object' && ++d < 2) {
2313 rc += this.spaces(d * 2) + i + ' -> ';
2315 rc += this.inspect(o[i], d);
2317 rc += '?' + '?' + '?\r\n';
2322 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2327 VT100.prototype.checkComposedKeys = function(event) {
2328 // Composed keys (at least on Linux) do not generate normal events.
2329 // Instead, they get entered into the text field. We normally catch
2330 // this on the next keyup event.
2331 var s = this.input.value;
2333 this.input.value = '';
2334 if (this.menu.style.visibility == 'hidden') {
2335 this.keysPressed(s);
2340 VT100.prototype.fixEvent = function(event) {
2341 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2342 // is used as a second-level selector, clear the modifier bits before
2343 // handling the event.
2344 if (event.ctrlKey && event.altKey) {
2346 fake.charCode = event.charCode;
2347 fake.keyCode = event.keyCode;
2348 fake.ctrlKey = false;
2349 fake.shiftKey = event.shiftKey;
2350 fake.altKey = false;
2351 fake.metaKey = event.metaKey;
2355 // Some browsers fail to translate keys, if both shift and alt/meta is
2356 // pressed at the same time. We try to translate those cases, but that
2357 // only works for US keyboard layouts.
2358 if (event.shiftKey) {
2361 switch (this.lastNormalKeyDownEvent.keyCode) {
2362 case 39: /* ' -> " */ u = 39; s = 34; break;
2363 case 44: /* , -> < */ u = 44; s = 60; break;
2364 case 45: /* - -> _ */ u = 45; s = 95; break;
2365 case 46: /* . -> > */ u = 46; s = 62; break;
2366 case 47: /* / -> ? */ u = 47; s = 63; break;
2368 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2369 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2370 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2371 case 51: /* 3 -> # */ u = 51; s = 35; break;
2372 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2373 case 53: /* 5 -> % */ u = 53; s = 37; break;
2374 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2375 case 55: /* 7 -> & */ u = 55; s = 38; break;
2376 case 56: /* 8 -> * */ u = 56; s = 42; break;
2377 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2379 case 59: /* ; -> : */ u = 59; s = 58; break;
2380 case 61: /* = -> + */ u = 61; s = 43; break;
2381 case 91: /* [ -> { */ u = 91; s = 123; break;
2382 case 92: /* \ -> | */ u = 92; s = 124; break;
2383 case 93: /* ] -> } */ u = 93; s = 125; break;
2384 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2386 case 109: /* - -> _ */ u = 45; s = 95; break;
2387 case 111: /* / -> ? */ u = 47; s = 63; break;
2389 case 186: /* ; -> : */ u = 59; s = 58; break;
2390 case 187: /* = -> + */ u = 61; s = 43; break;
2391 case 188: /* , -> < */ u = 44; s = 60; break;
2392 case 189: /* - -> _ */ u = 45; s = 95; break;
2393 case 190: /* . -> > */ u = 46; s = 62; break;
2394 case 191: /* / -> ? */ u = 47; s = 63; break;
2395 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2396 case 219: /* [ -> { */ u = 91; s = 123; break;
2397 case 220: /* \ -> | */ u = 92; s = 124; break;
2398 case 221: /* ] -> } */ u = 93; s = 125; break;
2399 case 222: /* ' -> " */ u = 39; s = 34; break;
2402 if (s && (event.charCode == u || event.charCode == 0)) {
2405 fake.keyCode = event.keyCode;
2406 fake.ctrlKey = event.ctrlKey;
2407 fake.shiftKey = event.shiftKey;
2408 fake.altKey = event.altKey;
2409 fake.metaKey = event.metaKey;
2416 VT100.prototype.keyDown = function(event) {
2417 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2418 // (event.shiftKey || event.ctrlKey || event.altKey ||
2419 // event.metaKey ? ', ' +
2420 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2421 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2423 this.checkComposedKeys(event);
2424 this.lastKeyPressedEvent = undefined;
2425 this.lastKeyDownEvent = undefined;
2426 this.lastNormalKeyDownEvent = event;
2429 event.keyCode == 32 ||
2430 event.keyCode >= 48 && event.keyCode <= 57 ||
2431 event.keyCode >= 65 && event.keyCode <= 90;
2434 event.keyCode >= 96 && event.keyCode <= 105 ||
2435 event.keyCode == 226;
2438 event.keyCode == 59 || event.keyCode == 61 ||
2439 event.keyCode == 106 || event.keyCode == 107 ||
2440 event.keyCode >= 109 && event.keyCode <= 111 ||
2441 event.keyCode >= 186 && event.keyCode <= 192 ||
2442 event.keyCode >= 219 && event.keyCode <= 222 ||
2443 event.keyCode == 252;
2445 if (navigator.appName == 'Konqueror') {
2446 normalKey |= event.keyCode < 128;
2451 // We normally prefer to look at keypress events, as they perform the
2452 // translation from keyCode to charCode. This is important, as the
2453 // translation is locale-dependent.
2454 // But for some keys, we must intercept them during the keydown event,
2455 // as they would otherwise get interpreted by the browser.
2456 // Even, when doing all of this, there are some keys that we can never
2457 // intercept. This applies to some of the menu navigation keys in IE.
2458 // In fact, we see them, but we cannot stop IE from seeing them, too.
2459 if ((event.charCode || event.keyCode) &&
2460 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2462 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2463 // interpret this sequence ourselves, as some keyboard layouts use
2464 // it for second-level layouts.
2465 !(event.ctrlKey && event.altKey)) ||
2466 this.catchModifiersEarly && normalKey && !alphNumKey &&
2467 (event.ctrlKey || event.altKey || event.metaKey) ||
2469 this.lastKeyDownEvent = event;
2471 fake.ctrlKey = event.ctrlKey;
2472 fake.shiftKey = event.shiftKey;
2473 fake.altKey = event.altKey;
2474 fake.metaKey = event.metaKey;
2476 fake.charCode = event.keyCode;
2480 fake.keyCode = event.keyCode;
2481 if (!alphNumKey && event.shiftKey) {
2482 fake = this.fixEvent(fake);
2486 this.handleKey(fake);
2487 this.lastNormalKeyDownEvent = undefined;
2490 // For non-IE browsers
2491 event.stopPropagation();
2492 event.preventDefault();
2497 event.cancelBubble = true;
2498 event.returnValue = false;
2508 VT100.prototype.keyPressed = function(event) {
2509 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2510 // (event.shiftKey || event.ctrlKey || event.altKey ||
2511 // event.metaKey ? ', ' +
2512 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2513 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2515 if (this.lastKeyDownEvent) {
2516 // If we already processed the key on keydown, do not process it
2517 // again here. Ideally, the browser should not even have generated a
2518 // keypress event in this case. But that does not appear to always work.
2519 this.lastKeyDownEvent = undefined;
2521 this.handleKey(event.altKey || event.metaKey
2522 ? this.fixEvent(event) : event);
2526 // For non-IE browsers
2527 event.preventDefault();
2533 event.cancelBubble = true;
2534 event.returnValue = false;
2539 this.lastNormalKeyDownEvent = undefined;
2540 this.lastKeyPressedEvent = event;
2544 VT100.prototype.keyUp = function(event) {
2545 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2546 // (event.shiftKey || event.ctrlKey || event.altKey ||
2547 // event.metaKey ? ', ' +
2548 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2549 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2551 if (this.lastKeyPressedEvent) {
2552 // The compose key on Linux occasionally confuses the browser and keeps
2553 // inserting bogus characters into the input field, even if just a regular
2554 // key has been pressed. Detect this case and drop the bogus characters.
2556 event.srcElement).value = '';
2558 // This is usually were we notice that a key has been composed and
2559 // thus failed to generate normal events.
2560 this.checkComposedKeys(event);
2562 // Some browsers don't report keypress events if ctrl or alt is pressed
2563 // for non-alphanumerical keys. Patch things up for now, but in the
2564 // future we will catch these keys earlier (in the keydown handler).
2565 if (this.lastNormalKeyDownEvent) {
2566 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
2567 this.catchModifiersEarly = true;
2569 event.keyCode == 32 ||
2570 event.keyCode >= 48 && event.keyCode <= 57 ||
2571 event.keyCode >= 65 && event.keyCode <= 90;
2574 event.keyCode >= 96 && event.keyCode <= 105;
2577 event.keyCode == 59 || event.keyCode == 61 ||
2578 event.keyCode == 106 || event.keyCode == 107 ||
2579 event.keyCode >= 109 && event.keyCode <= 111 ||
2580 event.keyCode >= 186 && event.keyCode <= 192 ||
2581 event.keyCode >= 219 && event.keyCode <= 222 ||
2582 event.keyCode == 252;
2584 fake.ctrlKey = event.ctrlKey;
2585 fake.shiftKey = event.shiftKey;
2586 fake.altKey = event.altKey;
2587 fake.metaKey = event.metaKey;
2589 fake.charCode = event.keyCode;
2593 fake.keyCode = event.keyCode;
2594 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2595 fake = this.fixEvent(fake);
2598 this.lastNormalKeyDownEvent = undefined;
2599 this.handleKey(fake);
2605 event.cancelBubble = true;
2606 event.returnValue = false;
2611 this.lastKeyDownEvent = undefined;
2612 this.lastKeyPressedEvent = undefined;
2616 VT100.prototype.animateCursor = function(inactive) {
2617 if (!this.cursorInterval) {
2618 this.cursorInterval = setInterval(
2621 vt100.animateCursor();
2623 // Use this opportunity to check whether the user entered a composed
2624 // key, or whether somebody pasted text into the textfield.
2625 vt100.checkComposedKeys();
2629 if (inactive != undefined || this.cursor.className != 'inactive') {
2631 this.cursor.className = 'inactive';
2633 this.cursor.className = this.cursor.className == 'bright'
2639 VT100.prototype.blurCursor = function() {
2640 this.animateCursor(true);
2643 VT100.prototype.focusCursor = function() {
2644 this.animateCursor(false);
2647 VT100.prototype.flashScreen = function() {
2648 this.isInverted = !this.isInverted;
2649 this.refreshInvertedState();
2650 this.isInverted = !this.isInverted;
2651 setTimeout(function(vt100) {
2653 vt100.refreshInvertedState();
2658 VT100.prototype.beep = function() {
2659 if (this.visualBell) {
2666 this.beeper.src = 'beep.wav';
2673 VT100.prototype.bs = function() {
2674 if (this.cursorX > 0) {
2675 this.gotoXY(this.cursorX - 1, this.cursorY);
2676 this.needWrap = false;
2680 VT100.prototype.ht = function(count) {
2681 if (count == undefined) {
2684 var cx = this.cursorX;
2685 while (count-- > 0) {
2686 while (cx++ < this.terminalWidth) {
2687 var tabState = this.userTabStop[cx];
2688 if (tabState == false) {
2689 // Explicitly cleared tab stop
2691 } else if (tabState) {
2692 // Explicitly set tab stop
2695 // Default tab stop at each eighth column
2702 if (cx > this.terminalWidth - 1) {
2703 cx = this.terminalWidth - 1;
2705 if (cx != this.cursorX) {
2706 this.gotoXY(cx, this.cursorY);
2710 VT100.prototype.rt = function(count) {
2711 if (count == undefined) {
2714 var cx = this.cursorX;
2715 while (count-- > 0) {
2717 var tabState = this.userTabStop[cx];
2718 if (tabState == false) {
2719 // Explicitly cleared tab stop
2721 } else if (tabState) {
2722 // Explicitly set tab stop
2725 // Default tab stop at each eighth column
2735 if (cx != this.cursorX) {
2736 this.gotoXY(cx, this.cursorY);
2740 VT100.prototype.cr = function() {
2741 this.gotoXY(0, this.cursorY);
2742 this.needWrap = false;
2745 VT100.prototype.lf = function(count) {
2746 if (count == undefined) {
2749 if (count > this.terminalHeight) {
2750 count = this.terminalHeight;
2756 while (count-- > 0) {
2757 if (this.cursorY == this.bottom - 1) {
2758 this.scrollRegion(0, this.top + 1,
2759 this.terminalWidth, this.bottom - this.top - 1,
2760 0, -1, this.color, this.style);
2762 } else if (this.cursorY < this.terminalHeight - 1) {
2763 this.gotoXY(this.cursorX, this.cursorY + 1);
2768 VT100.prototype.ri = function(count) {
2769 if (count == undefined) {
2772 if (count > this.terminalHeight) {
2773 count = this.terminalHeight;
2779 while (count-- > 0) {
2780 if (this.cursorY == this.top) {
2781 this.scrollRegion(0, this.top,
2782 this.terminalWidth, this.bottom - this.top - 1,
2783 0, 1, this.color, this.style);
2784 } else if (this.cursorY > 0) {
2785 this.gotoXY(this.cursorX, this.cursorY - 1);
2788 this.needWrap = false;
2791 VT100.prototype.respondID = function() {
2792 this.respondString += '\u001B[?6c';
2795 VT100.prototype.respondSecondaryDA = function() {
2796 this.respondString += '\u001B[>0;0;0c';
2800 VT100.prototype.updateStyle = function() {
2802 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2803 this.style = 'text-decoration:underline;';
2805 var bg = (this.attr >> 4) & 0xF;
2806 var fg = this.attr & 0xF;
2807 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2812 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2813 fg = 8; // Dark grey
2814 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2817 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2820 // Make some readability enhancements. Most notably, disallow identical
2821 // background and foreground colors.
2823 if ((fg ^= 8) == 7) {
2827 // And disallow bright colors on a light-grey background.
2828 if (bg == 7 && fg >= 8) {
2829 if ((fg -= 8) == 7) {
2834 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2837 VT100.prototype.setAttrColors = function(attr) {
2838 if (attr != this.attr) {
2844 VT100.prototype.saveCursor = function() {
2845 this.savedX[this.currentScreen] = this.cursorX;
2846 this.savedY[this.currentScreen] = this.cursorY;
2847 this.savedAttr[this.currentScreen] = this.attr;
2848 this.savedUseGMap = this.useGMap;
2849 for (var i = 0; i < 4; i++) {
2850 this.savedGMap[i] = this.GMap[i];
2852 this.savedValid[this.currentScreen] = true;
2855 VT100.prototype.restoreCursor = function() {
2856 if (!this.savedValid[this.currentScreen]) {
2859 this.attr = this.savedAttr[this.currentScreen];
2861 this.useGMap = this.savedUseGMap;
2862 for (var i = 0; i < 4; i++) {
2863 this.GMap[i] = this.savedGMap[i];
2865 this.translate = this.GMap[this.useGMap];
2866 this.needWrap = false;
2867 this.gotoXY(this.savedX[this.currentScreen],
2868 this.savedY[this.currentScreen]);
2871 VT100.prototype.set80_132Mode = function(state) {
2872 var transform = undefined;
2873 var styles = [ 'transform',
2878 for (var i = 0; i < styles.length; ++i) {
2879 if (typeof this.console[0].style[styles[i]] != 'undefined') {
2880 transform = styles[i];
2886 if ((this.console[this.currentScreen].style[transform] != '') == state) {
2890 state ? transform == 'filter'
2891 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
2892 'M11=0.606060606060606060606,M12=0,M21=0,M22=1,' +
2893 "sizingMethod='auto expand')"
2894 : 'translateX(-50%) ' +
2895 'scaleX(0.606060606060606060606) ' +
2898 this.console[this.currentScreen].style[transform] = style;
2899 this.cursor.style[transform] = style;
2900 this.space.style[transform] = style;
2901 this.scale = state ? 1.65 : 1.0;
2902 if (transform == 'filter') {
2903 this.console[this.currentScreen].style.width = state ? '165%' : '';
2909 VT100.prototype.setMode = function(state) {
2910 for (var i = 0; i <= this.npar; i++) {
2911 if (this.isQuestionMark) {
2912 switch (this.par[i]) {
2913 case 1: this.cursorKeyMode = state; break;
2914 case 3: this.set80_132Mode(state); break;
2915 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2916 case 6: this.offsetMode = state; break;
2917 case 7: this.autoWrapMode = state; break;
2919 case 9: this.mouseReporting = state; break;
2920 case 25: this.cursorNeedsShowing = state;
2921 if (state) { this.showCursor(); }
2922 else { this.hideCursor(); } break;
2925 case 47: this.enableAlternateScreen(state); break;
2929 switch (this.par[i]) {
2930 case 3: this.dispCtrl = state; break;
2931 case 4: this.insertMode = state; break;
2932 case 20:this.crLfMode = state; break;
2939 VT100.prototype.statusReport = function() {
2940 // Ready and operational.
2941 this.respondString += '\u001B[0n';
2944 VT100.prototype.cursorReport = function() {
2945 this.respondString += '\u001B[' +
2946 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2948 (this.cursorX + 1) +
2952 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2953 // Changing of cursor color is not implemented.
2956 VT100.prototype.openPrinterWindow = function() {
2959 if (!this.printWin || this.printWin.closed) {
2960 this.printWin = window.open('', 'print-output',
2961 'width=800,height=600,directories=no,location=no,menubar=yes,' +
2962 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
2963 this.printWin.document.body.innerHTML =
2964 '<link rel="stylesheet" href="' +
2965 document.location.protocol + '//' + document.location.host +
2966 document.location.pathname.replace(/[^/]*$/, '') +
2967 'print-styles.css" type="text/css">\n' +
2968 '<div id="options"><input id="autoprint" type="checkbox"' +
2969 (this.autoprint ? ' checked' : '') + '>' +
2970 'Automatically, print page(s) when job is ready' +
2971 '</input></div>\n' +
2972 '<div id="spacer"><input type="checkbox"> </input></div>' +
2973 '<pre id="print"></pre>\n';
2974 var autoprint = this.printWin.document.getElementById('autoprint');
2975 this.addListener(autoprint, 'click',
2976 (function(vt100, autoprint) {
2978 vt100.autoprint = autoprint.checked;
2979 vt100.storeUserSettings();
2982 })(this, autoprint));
2983 this.printWin.document.title = 'ShellInABox Printer Output';
2986 // Maybe, a popup blocker prevented us from working. Better catch the
2987 // exception, so that we won't break the entire terminal session. The
2988 // user probably needs to disable the blocker first before retrying the
2992 rc &= this.printWin && !this.printWin.closed &&
2993 (this.printWin.innerWidth ||
2994 this.printWin.document.documentElement.clientWidth ||
2995 this.printWin.document.body.clientWidth) > 1;
2997 if (!rc && this.printing == 100) {
2998 // Different popup blockers work differently. We try to detect a couple
2999 // of common methods. And then we retry again a brief amount later, as
3000 // false positives are otherwise possible. If we are sure that there is
3001 // a popup blocker in effect, we alert the user to it. This is helpful
3002 // as some popup blockers have minimal or no UI, and the user might not
3003 // notice that they are missing the popup. In any case, we only show at
3004 // most one message per print job.
3005 this.printing = true;
3006 setTimeout((function(win) {
3008 if (!win || win.closed ||
3010 win.document.documentElement.clientWidth ||
3011 win.document.body.clientWidth) <= 1) {
3012 alert('Attempted to print, but a popup blocker ' +
3013 'prevented the printer window from opening');
3016 })(this.printWin), 2000);
3021 VT100.prototype.sendToPrinter = function(s) {
3022 this.openPrinterWindow();
3024 var doc = this.printWin.document;
3025 var print = doc.getElementById('print');
3026 if (print.lastChild && print.lastChild.nodeName == '#text') {
3027 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3029 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3032 // There probably was a more aggressive popup blocker that prevented us
3033 // from accessing the printer windows.
3037 VT100.prototype.sendControlToPrinter = function(ch) {
3038 // We get called whenever doControl() is active. But for the printer, we
3039 // only implement a basic line printer that doesn't understand most of
3040 // the escape sequences of the VT100 terminal. In fact, the only escape
3041 // sequence that we really need to recognize is '^[[5i' for turning the
3047 this.openPrinterWindow();
3048 var doc = this.printWin.document;
3049 var print = doc.getElementById('print');
3050 var chars = print.lastChild &&
3051 print.lastChild.nodeName == '#text' ?
3052 print.lastChild.textContent.length : 0;
3053 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3060 this.openPrinterWindow();
3061 var pageBreak = this.printWin.document.createElement('div');
3062 pageBreak.className = 'pagebreak';
3063 pageBreak.innerHTML = '<hr />';
3064 this.printWin.document.getElementById('print').appendChild(pageBreak);
3068 this.openPrinterWindow();
3069 var lineBreak = this.printWin.document.createElement('br');
3070 this.printWin.document.getElementById('print').appendChild(lineBreak);
3074 this.isEsc = 1 /* ESesc */;
3077 switch (this.isEsc) {
3079 this.isEsc = 0 /* ESnormal */;
3082 this.isEsc = 2 /* ESsquare */;
3088 case 2 /* ESsquare */:
3090 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3091 0, 0, 0, 0, 0, 0, 0, 0 ];
3092 this.isEsc = 3 /* ESgetpars */;
3093 this.isQuestionMark = ch == 0x3F /*?*/;
3094 if (this.isQuestionMark) {
3098 case 3 /* ESgetpars */:
3099 if (ch == 0x3B /*;*/) {
3102 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3103 var par = this.par[this.npar];
3104 if (par == undefined) {
3107 this.par[this.npar] = 10*par + (ch & 0xF);
3110 this.isEsc = 4 /* ESgotpars */;
3113 case 4 /* ESgotpars */:
3114 this.isEsc = 0 /* ESnormal */;
3115 if (this.isQuestionMark) {
3120 this.csii(this.par[0]);
3127 this.isEsc = 0 /* ESnormal */;
3133 // There probably was a more aggressive popup blocker that prevented us
3134 // from accessing the printer windows.
3138 VT100.prototype.csiAt = function(number) {
3143 if (number > this.terminalWidth - this.cursorX) {
3144 number = this.terminalWidth - this.cursorX;
3146 this.scrollRegion(this.cursorX, this.cursorY,
3147 this.terminalWidth - this.cursorX - number, 1,
3148 number, 0, this.color, this.style);
3149 this.needWrap = false;
3152 VT100.prototype.csii = function(number) {
3155 case 0: // Print Screen
3158 case 4: // Stop printing
3160 if (this.printing && this.printWin && !this.printWin.closed) {
3161 var print = this.printWin.document.getElementById('print');
3162 while (print.lastChild &&
3163 print.lastChild.tagName == 'DIV' &&
3164 print.lastChild.className == 'pagebreak') {
3165 // Remove trailing blank pages
3166 print.removeChild(print.lastChild);
3168 if (this.autoprint) {
3169 this.printWin.print();
3174 this.printing = false;
3176 case 5: // Start printing
3177 if (!this.printing && this.printWin && !this.printWin.closed) {
3178 this.printWin.document.getElementById('print').innerHTML = '';
3180 this.printing = 100;
3187 VT100.prototype.csiJ = function(number) {
3189 case 0: // Erase from cursor to end of display
3190 this.clearRegion(this.cursorX, this.cursorY,
3191 this.terminalWidth - this.cursorX, 1,
3192 this.color, this.style);
3193 if (this.cursorY < this.terminalHeight-2) {
3194 this.clearRegion(0, this.cursorY+1,
3195 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3196 this.color, this.style);
3199 case 1: // Erase from start to cursor
3200 if (this.cursorY > 0) {
3201 this.clearRegion(0, 0,
3202 this.terminalWidth, this.cursorY,
3203 this.color, this.style);
3205 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3206 this.color, this.style);
3208 case 2: // Erase whole display
3209 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3210 this.color, this.style);
3218 VT100.prototype.csiK = function(number) {
3220 case 0: // Erase from cursor to end of line
3221 this.clearRegion(this.cursorX, this.cursorY,
3222 this.terminalWidth - this.cursorX, 1,
3223 this.color, this.style);
3225 case 1: // Erase from start of line to cursor
3226 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3227 this.color, this.style);
3229 case 2: // Erase whole line
3230 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3231 this.color, this.style);
3239 VT100.prototype.csiL = function(number) {
3240 // Open line by inserting blank line(s)
3241 if (this.cursorY >= this.bottom) {
3247 if (number > this.bottom - this.cursorY) {
3248 number = this.bottom - this.cursorY;
3250 this.scrollRegion(0, this.cursorY,
3251 this.terminalWidth, this.bottom - this.cursorY - number,
3252 0, number, this.color, this.style);
3256 VT100.prototype.csiM = function(number) {
3257 // Delete line(s), scrolling up the bottom of the screen.
3258 if (this.cursorY >= this.bottom) {
3264 if (number > this.bottom - this.cursorY) {
3265 number = bottom - cursorY;
3267 this.scrollRegion(0, this.cursorY + number,
3268 this.terminalWidth, this.bottom - this.cursorY - number,
3269 0, -number, this.color, this.style);
3273 VT100.prototype.csim = function() {
3274 for (var i = 0; i <= this.npar; i++) {
3275 switch (this.par[i]) {
3276 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
3277 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
3278 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
3279 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
3280 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
3281 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
3283 this.translate = this.GMap[this.useGMap];
3284 this.dispCtrl = false;
3285 this.toggleMeta = false;
3288 this.translate = this.CodePage437Map;
3289 this.dispCtrl = true;
3290 this.toggleMeta = false;
3293 this.translate = this.CodePage437Map;
3294 this.dispCtrl = true;
3295 this.toggleMeta = true;
3298 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
3299 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
3300 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
3301 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
3302 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
3303 0x0200 /* ATTR_UNDERLINE */; break;
3304 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
3305 case 49: this.attr |= 0xF0; break;
3307 if (this.par[i] >= 30 && this.par[i] <= 37) {
3308 var fg = this.par[i] - 30;
3309 this.attr = (this.attr & ~0x0F) | fg;
3310 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3311 var bg = this.par[i] - 40;
3312 this.attr = (this.attr & ~0xF0) | (bg << 4);
3320 VT100.prototype.csiP = function(number) {
3321 // Delete character(s) following cursor
3325 if (number > this.terminalWidth - this.cursorX) {
3326 number = this.terminalWidth - this.cursorX;
3328 this.scrollRegion(this.cursorX + number, this.cursorY,
3329 this.terminalWidth - this.cursorX - number, 1,
3330 -number, 0, this.color, this.style);
3334 VT100.prototype.csiX = function(number) {
3335 // Clear characters following cursor
3339 if (number > this.terminalWidth - this.cursorX) {
3340 number = this.terminalWidth - this.cursorX;
3342 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3343 this.color, this.style);
3347 VT100.prototype.settermCommand = function() {
3348 // Setterm commands are not implemented
3351 VT100.prototype.doControl = function(ch) {
3352 if (this.printing) {
3353 this.sendControlToPrinter(ch);
3358 case 0x00: /* ignored */ break;
3359 case 0x08: this.bs(); break;
3360 case 0x09: this.ht(); break;
3364 case 0x84: this.lf(); if (!this.crLfMode) break;
3365 case 0x0D: this.cr(); break;
3366 case 0x85: this.cr(); this.lf(); break;
3367 case 0x0E: this.useGMap = 1;
3368 this.translate = this.GMap[1];
3369 this.dispCtrl = true; break;
3370 case 0x0F: this.useGMap = 0;
3371 this.translate = this.GMap[0];
3372 this.dispCtrl = false; break;
3374 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3375 case 0x1B: this.isEsc = 1 /* ESesc */; break;
3376 case 0x7F: /* ignored */ break;
3377 case 0x88: this.userTabStop[this.cursorX] = true; break;
3378 case 0x8D: this.ri(); break;
3379 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3380 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3381 case 0x9A: this.respondID(); break;
3382 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3383 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
3387 default: switch (this.isEsc) {
3389 this.isEsc = 0 /* ESnormal */;
3391 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3392 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3394 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3396 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3398 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3399 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3400 /*7*/ case 0x37: this.saveCursor(); break;
3401 /*8*/ case 0x38: this.restoreCursor(); break;
3402 /*>*/ case 0x3E: this.applKeyMode = false; break;
3403 /*=*/ case 0x3D: this.applKeyMode = true; break;
3404 /*D*/ case 0x44: this.lf(); break;
3405 /*E*/ case 0x45: this.cr(); this.lf(); break;
3406 /*M*/ case 0x4D: this.ri(); break;
3407 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3408 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3409 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3410 /*Z*/ case 0x5A: this.respondID(); break;
3411 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3412 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3413 /*c*/ case 0x63: this.reset(); break;
3414 /*g*/ case 0x67: this.flashScreen(); break;
3418 case 15 /* ESnonstd */:
3422 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3423 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3424 this.isEsc = 16 /* ESpalette */; break;
3425 /*R*/ case 0x52: // Palette support is not implemented
3426 this.isEsc = 0 /* ESnormal */; break;
3427 default: this.isEsc = 0 /* ESnormal */; break;
3430 case 16 /* ESpalette */:
3431 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3432 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3433 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3434 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3436 if (this.npar == 7) {
3437 // Palette support is not implemented
3438 this.isEsc = 0 /* ESnormal */;
3441 this.isEsc = 0 /* ESnormal */;
3444 case 2 /* ESsquare */:
3446 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3447 0, 0, 0, 0, 0, 0, 0, 0 ];
3448 this.isEsc = 3 /* ESgetpars */;
3449 /*[*/ if (ch == 0x5B) { // Function key
3450 this.isEsc = 6 /* ESfunckey */;
3453 /*?*/ this.isQuestionMark = ch == 0x3F;
3454 if (this.isQuestionMark) {
3459 case 5 /* ESdeviceattr */:
3460 case 3 /* ESgetpars */:
3461 /*;*/ if (ch == 0x3B) {
3464 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3465 var par = this.par[this.npar];
3466 if (par == undefined) {
3469 this.par[this.npar] = 10*par + (ch & 0xF);
3471 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3473 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3474 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3475 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3476 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3479 this.isEsc = 0 /* ESnormal */;
3482 this.isEsc = 4 /* ESgotpars */;
3485 case 4 /* ESgotpars */:
3486 this.isEsc = 0 /* ESnormal */;
3487 if (this.isQuestionMark) {
3489 /*h*/ case 0x68: this.setMode(true); break;
3490 /*l*/ case 0x6C: this.setMode(false); break;
3491 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3494 this.isQuestionMark = false;
3498 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3499 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3501 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3502 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3503 this.cursorY - (this.par[0] ? this.par[0] : 1));
3506 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3507 this.cursorY + (this.par[0] ? this.par[0] : 1));
3510 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3511 this.cursorY); break;
3512 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3513 this.cursorY); break;
3514 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3516 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3518 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3520 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3521 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3522 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3523 /*i*/ case 0x69: this.csii(this.par[0]); break;
3524 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3525 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3526 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3527 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3528 /*m*/ case 0x6D: this.csim(); break;
3529 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3530 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3531 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3532 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3533 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3534 /*g*/ case 0x67: if (this.par[0] == 0) {
3535 this.userTabStop[this.cursorX] = false;
3536 } else if (this.par[0] == 2 || this.par[0] == 3) {
3537 this.userTabStop = [ ];
3538 for (var i = 0; i < this.terminalWidth; i++) {
3539 this.userTabStop[i] = false;
3543 /*h*/ case 0x68: this.setMode(true); break;
3544 /*l*/ case 0x6C: this.setMode(false); break;
3545 /*n*/ case 0x6E: switch (this.par[0]) {
3546 case 5: this.statusReport(); break;
3547 case 6: this.cursorReport(); break;
3551 /*q*/ case 0x71: // LED control not implemented
3553 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3554 var b = this.par[1] ? this.par[1]
3555 : this.terminalHeight;
3556 if (t < b && b <= this.terminalHeight) {
3562 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3563 if (c > this.terminalWidth * this.terminalHeight) {
3564 c = this.terminalWidth * this.terminalHeight;
3567 lineBuf += this.lastCharacter;
3570 /*s*/ case 0x73: this.saveCursor(); break;
3571 /*u*/ case 0x75: this.restoreCursor(); break;
3572 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3573 /*]*/ case 0x5D: this.settermCommand(); break;
3577 case 12 /* ESbang */:
3581 this.isEsc = 0 /* ESnormal */;
3583 case 13 /* ESpercent */:
3584 this.isEsc = 0 /* ESnormal */;
3586 /*@*/ case 0x40: this.utfEnabled = false; break;
3588 /*8*/ case 0x38: this.utfEnabled = true; break;
3592 case 6 /* ESfunckey */:
3593 this.isEsc = 0 /* ESnormal */; break;
3594 case 7 /* EShash */:
3595 this.isEsc = 0 /* ESnormal */;
3596 /*8*/ if (ch == 0x38) {
3597 // Screen alignment test not implemented
3600 case 8 /* ESsetG0 */:
3601 case 9 /* ESsetG1 */:
3602 case 10 /* ESsetG2 */:
3603 case 11 /* ESsetG3 */:
3604 var g = this.isEsc - 8 /* ESsetG0 */;
3605 this.isEsc = 0 /* ESnormal */;
3607 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3609 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3610 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3611 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3614 if (this.useGMap == g) {
3615 this.translate = this.GMap[g];
3618 case 17 /* ESstatus */:
3620 if (this.statusString && this.statusString.charAt(0) == ';') {
3621 this.statusString = this.statusString.substr(1);
3624 window.status = this.statusString;
3627 this.isEsc = 0 /* ESnormal */;
3629 this.statusString += String.fromCharCode(ch);
3632 case 18 /* ESss2 */:
3633 case 19 /* ESss3 */:
3635 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3636 [this.toggleMeta ? (ch | 0x80) : ch];
3637 if ((ch & 0xFF00) == 0xF000) {
3639 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3640 this.isEsc = 0 /* ESnormal */; break;
3643 this.lastCharacter = String.fromCharCode(ch);
3644 lineBuf += this.lastCharacter;
3645 this.isEsc = 0 /* ESnormal */; break;
3647 this.isEsc = 0 /* ESnormal */; break;
3654 VT100.prototype.renderString = function(s, showCursor) {
3655 if (this.printing) {
3656 this.sendToPrinter(s);
3663 // We try to minimize the number of DOM operations by coalescing individual
3664 // characters into strings. This is a significant performance improvement.
3665 var incX = s.length;
3666 if (incX > this.terminalWidth - this.cursorX) {
3667 incX = this.terminalWidth - this.cursorX;
3671 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3674 // Minimize the number of calls to putString(), by avoiding a direct
3675 // call to this.showCursor()
3676 this.cursor.style.visibility = '';
3678 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3681 VT100.prototype.vt100 = function(s) {
3682 this.cursorNeedsShowing = this.hideCursor();
3683 this.respondString = '';
3685 for (var i = 0; i < s.length; i++) {
3686 var ch = s.charCodeAt(i);
3687 if (this.utfEnabled) {
3688 // Decode UTF8 encoded character
3690 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3691 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3692 if (--this.utfCount <= 0) {
3693 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3702 if ((ch & 0xE0) == 0xC0) {
3704 this.utfChar = ch & 0x1F;
3705 } else if ((ch & 0xF0) == 0xE0) {
3707 this.utfChar = ch & 0x0F;
3708 } else if ((ch & 0xF8) == 0xF0) {
3710 this.utfChar = ch & 0x07;
3711 } else if ((ch & 0xFC) == 0xF8) {
3713 this.utfChar = ch & 0x03;
3714 } else if ((ch & 0xFE) == 0xFC) {
3716 this.utfChar = ch & 0x01;
3726 var isNormalCharacter =
3727 (ch >= 32 && ch <= 127 || ch >= 160 ||
3728 this.utfEnabled && ch >= 128 ||
3729 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3730 (ch != 0x7F || this.dispCtrl);
3732 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3734 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3736 if ((ch & 0xFF00) == 0xF000) {
3738 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3741 if (!this.printing) {
3742 if (this.needWrap || this.insertMode) {
3744 this.renderString(lineBuf);
3748 if (this.needWrap) {
3749 this.cr(); this.lf();
3751 if (this.insertMode) {
3752 this.scrollRegion(this.cursorX, this.cursorY,
3753 this.terminalWidth - this.cursorX - 1, 1,
3754 1, 0, this.color, this.style);
3757 this.lastCharacter = String.fromCharCode(ch);
3758 lineBuf += this.lastCharacter;
3759 if (!this.printing &&
3760 this.cursorX + lineBuf.length >= this.terminalWidth) {
3761 this.needWrap = this.autoWrapMode;
3765 this.renderString(lineBuf);
3768 var expand = this.doControl(ch);
3769 if (expand.length) {
3770 var r = this.respondString;
3771 this.respondString= r + this.vt100(expand);
3776 this.renderString(lineBuf, this.cursorNeedsShowing);
3777 } else if (this.cursorNeedsShowing) {
3780 return this.respondString;
3783 VT100.prototype.Latin1Map = [
3784 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3785 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3786 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3787 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3788 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3789 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3790 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3791 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3792 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3793 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3794 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3795 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3796 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3797 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3798 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3799 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3800 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3801 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3802 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3803 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3804 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3805 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3806 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3807 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3808 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3809 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3810 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3811 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3812 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3813 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3814 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3815 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3818 VT100.prototype.VT100GraphicsMap = [
3819 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3820 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3821 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3822 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3823 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3824 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3825 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3826 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3827 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3828 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3829 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3830 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3831 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3832 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3833 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3834 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3835 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3836 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3837 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3838 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3839 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3840 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3841 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3842 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3843 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3844 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3845 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3846 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3847 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3848 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3849 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3850 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3853 VT100.prototype.CodePage437Map = [
3854 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3855 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3856 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3857 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3858 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3859 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3860 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3861 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3862 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3863 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3864 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3865 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3866 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3867 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3868 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3869 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3870 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3871 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3872 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3873 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3874 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3875 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3876 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3877 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3878 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3879 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3880 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3881 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3882 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3883 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3884 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3885 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3888 VT100.prototype.DirectToFontMap = [
3889 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3890 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3891 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3892 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3893 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3894 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3895 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3896 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3897 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3898 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3899 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3900 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3901 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3902 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3903 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3904 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3905 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3906 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3907 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3908 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3909 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3910 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3911 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3912 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3913 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3914 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3915 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3916 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3917 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3918 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3919 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3920 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3923 VT100.prototype.ctrlAction = [
3924 true, false, false, false, false, false, false, true,
3925 true, true, true, true, true, true, true, true,
3926 false, false, false, false, false, false, false, false,
3927 true, false, true, true, false, false, false, false
3930 VT100.prototype.ctrlAlways = [
3931 true, false, false, false, false, false, false, false,
3932 true, false, true, false, true, true, true, true,
3933 false, false, false, false, false, false, false, false,
3934 false, false, false, true, false, false, false, false