1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
69 // #define ESgetpars 3
70 // #define ESgotpars 4
71 // #define ESdeviceattr 5
72 // #define ESfunckey 6
79 // #define ESpercent 13
80 // #define ESignore 14
81 // #define ESnonstd 15
82 // #define ESpalette 16
83 // #define ESstatus 17
87 // #define ATTR_DEFAULT 0x00F0
88 // #define ATTR_REVERSE 0x0100
89 // #define ATTR_UNDERLINE 0x0200
90 // #define ATTR_DIM 0x0400
91 // #define ATTR_BRIGHT 0x0800
92 // #define ATTR_BLINK 0x1000
94 // #define MOUSE_DOWN 0
96 // #define MOUSE_CLICK 2
98 function VT100(container) {
99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
106 // Optionally allow username and passwords.
107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
110 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
115 '(?::[1-9][0-9]*)?' +
118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
125 // Hostnames must end with a well-known top-level domain or must be
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
146 '(?::[1-9][0-9]{0,4})?' +
149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
155 '[-_.+a-zA-Z0-9]+@' +
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
173 // Optional arguments
174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
176 this.getUserSettings();
177 this.initializeElements(container);
178 this.maxScrollbackLines = 500;
181 this.isQuestionMark = false;
184 this.savedAttr = [ ];
185 this.savedUseGMap = 0;
186 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187 this.CodePage437Map, this.DirectToFontMap ];
188 this.savedValid = [ ];
189 this.respondString = '';
190 this.statusString = '';
191 this.internalClipboard = undefined;
195 VT100.prototype.reset = function(clearHistory) {
196 this.isEsc = 0 /* ESnormal */;
197 this.needWrap = false;
198 this.autoWrapMode = true;
199 this.dispCtrl = false;
200 this.toggleMeta = false;
201 this.insertMode = false;
202 this.applKeyMode = false;
203 this.cursorKeyMode = false;
204 this.crLfMode = false;
205 this.offsetMode = false;
206 this.mouseReporting = false;
207 this.printing = false;
208 if (typeof this.printWin != 'undefined' &&
209 this.printWin && !this.printWin.closed) {
210 this.printWin.close();
212 this.printWin = null;
213 this.utfEnabled = this.utfPreferred;
216 this.color = 'ansi0 bgAnsi15';
218 this.attr = 0x00F0 /* ATTR_DEFAULT */;
220 this.GMap = [ this.Latin1Map,
221 this.VT100GraphicsMap,
223 this.DirectToFontMap];
224 this.translate = this.GMap[this.useGMap];
226 this.bottom = this.terminalHeight;
227 this.lastCharacter = ' ';
228 this.userTabStop = [ ];
231 for (var i = 0; i < 2; i++) {
232 while (this.console[i].firstChild) {
233 this.console[i].removeChild(this.console[i].firstChild);
238 this.enableAlternateScreen(false);
240 var wasCompressed = false;
241 var styles = [ 'transform',
245 for (var i = 0; i < styles.length; ++i) {
246 if (typeof this.console[0].style[styles[i]] != 'undefined') {
247 for (var j = 0; j < 1; ++j) {
248 wasCompressed |= this.console[j].style[styles[i]] != '';
249 this.console[j].style[styles[i]] = '';
251 this.cursor.style[styles[i]] = '';
252 this.space.style[styles[i]] = '';
253 if (styles[i] == 'filter') {
254 this.console[this.currentScreen].style.width = '';
266 this.isInverted = false;
267 this.refreshInvertedState();
268 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
269 this.color, this.style);
272 VT100.prototype.addListener = function(elem, event, listener) {
273 if (elem.addEventListener) {
274 elem.addEventListener(event, listener, false);
276 elem.attachEvent('on' + event, listener);
280 VT100.prototype.getUserSettings = function() {
281 // Compute hash signature to identify the entries in the userCSS menu.
282 // If the menu is unchanged from last time, default values can be
283 // looked up in a cookie associated with this page.
285 this.utfPreferred = true;
286 this.visualBell = typeof suppressAllAudio != 'undefined' &&
288 this.autoprint = true;
289 this.blinkingCursor = true;
290 if (this.visualBell) {
291 this.signature = Math.floor(16807*this.signature + 1) %
294 if (typeof userCSSList != 'undefined') {
295 for (var i = 0; i < userCSSList.length; ++i) {
296 var label = userCSSList[i][0];
297 for (var j = 0; j < label.length; ++j) {
298 this.signature = Math.floor(16807*this.signature+
299 label.charCodeAt(j)) %
302 if (userCSSList[i][1]) {
303 this.signature = Math.floor(16807*this.signature + 1) %
309 var key = 'shellInABox=' + this.signature + ':';
310 var settings = document.cookie.indexOf(key);
312 settings = document.cookie.substr(settings + key.length).
313 replace(/([0-1]*).*/, "$1");
314 if (settings.length == 3 + (typeof userCSSList == 'undefined' ?
315 0 : userCSSList.length)) {
316 this.utfPreferred = settings.charAt(0) != '0';
317 this.visualBell = settings.charAt(1) != '0';
318 this.autoprint = settings.charAt(2) != '0';
319 this.blinkingCursor = settings.charAt(3) != '0';
320 if (typeof userCSSList != 'undefined') {
321 for (var i = 0; i < userCSSList.length; ++i) {
322 userCSSList[i][2] = settings.charAt(i + 3) != '0';
327 this.utfEnabled = this.utfPreferred;
330 VT100.prototype.storeUserSettings = function() {
331 var settings = 'shellInABox=' + this.signature + ':' +
332 (this.utfEnabled ? '1' : '0') +
333 (this.visualBell ? '1' : '0') +
334 (this.autoprint ? '1' : '0') +
335 (this.blinkingCursor ? '1' : '0');
336 if (typeof userCSSList != 'undefined') {
337 for (var i = 0; i < userCSSList.length; ++i) {
338 settings += userCSSList[i][2] ? '1' : '0';
342 d.setDate(d.getDate() + 3653);
343 document.cookie = settings + ';expires=' + d.toGMTString();
346 VT100.prototype.initializeUserCSSStyles = function() {
347 this.usercssActions = [];
348 if (typeof userCSSList != 'undefined') {
351 var wasSingleSel = 1;
352 var beginOfGroup = 0;
353 for (var i = 0; i <= userCSSList.length; ++i) {
354 if (i < userCSSList.length) {
355 var label = userCSSList[i][0];
356 var newGroup = userCSSList[i][1];
357 var enabled = userCSSList[i][2];
359 // Add user style sheet to document
360 var style = document.createElement('link');
361 var id = document.createAttribute('id');
362 id.nodeValue = 'usercss-' + i;
363 style.setAttributeNode(id);
364 var rel = document.createAttribute('rel');
365 rel.nodeValue = 'stylesheet';
366 style.setAttributeNode(rel);
367 var href = document.createAttribute('href');
368 href.nodeValue = 'usercss-' + i + '.css';
369 style.setAttributeNode(href);
370 var type = document.createAttribute('type');
371 type.nodeValue = 'text/css';
372 style.setAttributeNode(type);
373 document.getElementsByTagName('head')[0].appendChild(style);
374 style.disabled = !enabled;
378 if (newGroup || i == userCSSList.length) {
379 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
380 // The last group had multiple entries that are mutually exclusive;
381 // or the previous to last group did. In either case, we need to
382 // append a "<hr />" before we can add the last group to the menu.
385 wasSingleSel = i - beginOfGroup < 1;
389 for (var j = beginOfGroup; j < i; ++j) {
390 this.usercssActions[this.usercssActions.length] =
391 function(vt100, current, begin, count) {
393 // Deselect all other entries in the group, then either select
394 // (for multiple entries in group) or toggle (for on/off entry)
395 // the current entry.
397 var entry = vt100.getChildById(vt100.menu,
401 for (var c = count; c > 0; ++j) {
402 if (entry.tagName == 'LI') {
405 var label = vt100.usercss.childNodes[j];
407 // Restore label to just the text content
408 if (typeof label.textContent == 'undefined') {
409 var s = label.innerText;
410 label.innerHTML = '';
411 label.appendChild(document.createTextNode(s));
413 label.textContent= label.textContent;
416 // User style sheets are number sequentially
417 var sheet = document.getElementById(
421 sheet.disabled = !sheet.disabled;
423 sheet.disabled = false;
425 if (!sheet.disabled) {
426 label.innerHTML= '<img src="enabled.gif" />' +
430 sheet.disabled = true;
432 userCSSList[i][2] = !sheet.disabled;
435 entry = entry.nextSibling;
438 }(this, j, beginOfGroup, i - beginOfGroup);
441 if (i == userCSSList.length) {
447 // Collect all entries in a group, before attaching them to the menu.
448 // This is necessary as we don't know whether this is a group of
449 // mutually exclusive options (which should be separated by "<hr />" on
450 // both ends), or whether this is a on/off toggle, which can be grouped
451 // together with other on/off options.
453 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
457 this.usercss.innerHTML = menu;
461 VT100.prototype.initializeElements = function(container) {
462 // If the necessary objects have not already been defined in the HTML
463 // page, create them now.
465 this.container = container;
466 } else if (!(this.container = document.getElementById('vt100'))) {
467 this.container = document.createElement('div');
468 this.container.id = 'vt100';
469 document.body.appendChild(this.container);
472 if (!this.getChildById(this.container, 'reconnect') ||
473 !this.getChildById(this.container, 'menu') ||
474 !this.getChildById(this.container, 'scrollable') ||
475 !this.getChildById(this.container, 'console') ||
476 !this.getChildById(this.container, 'alt_console') ||
477 !this.getChildById(this.container, 'ieprobe') ||
478 !this.getChildById(this.container, 'padding') ||
479 !this.getChildById(this.container, 'cursor') ||
480 !this.getChildById(this.container, 'lineheight') ||
481 !this.getChildById(this.container, 'usercss') ||
482 !this.getChildById(this.container, 'space') ||
483 !this.getChildById(this.container, 'input') ||
484 !this.getChildById(this.container, 'cliphelper')) {
485 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
486 // we might get a pointless warning that a suitable plugin is not yet
487 // installed. If in doubt, we'd rather just stay silent.
490 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
492 embed = typeof suppressAllAudio != 'undefined' &&
493 suppressAllAudio ? "" :
494 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
497 'autostart="false" ' +
499 'enablejavascript="true" ' +
500 'type="audio/x-wav" ' +
503 'style="position:absolute;left:-1000px;top:-1000px" />';
508 this.container.innerHTML =
509 '<div id="reconnect" style="visibility: hidden">' +
510 '<input type="button" value="Connect" ' +
511 'onsubmit="return false" />' +
513 '<div id="cursize" style="visibility: hidden">' +
515 '<div id="menu"></div>' +
516 '<div id="scrollable">' +
517 '<pre id="lineheight"> </pre>' +
518 '<pre id="console">' +
520 '<div id="ieprobe"><span> </span></div>' +
522 '<pre id="alt_console" style="display: none"></pre>' +
523 '<div id="padding"></div>' +
524 '<pre id="cursor"> </pre>' +
526 '<div class="hidden">' +
527 '<div id="usercss"></div>' +
528 '<pre><div><span id="space"></span></div></pre>' +
529 '<input type="textfield" id="input" />' +
530 '<input type="textfield" id="cliphelper" />' +
531 (typeof suppressAllAudio != 'undefined' &&
532 suppressAllAudio ? "" :
533 embed + '<bgsound id="beep_bgsound" loop=1 />') +
537 // Find the object used for playing the "beep" sound, if any.
538 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
539 this.beeper = undefined;
541 this.beeper = this.getChildById(this.container,
543 if (!this.beeper || !this.beeper.Play) {
544 this.beeper = this.getChildById(this.container,
546 if (!this.beeper || typeof this.beeper.src == 'undefined') {
547 this.beeper = undefined;
552 // Initialize the variables for finding the text console and the
554 this.reconnectBtn = this.getChildById(this.container,'reconnect');
555 this.curSizeBox = this.getChildById(this.container, 'cursize');
556 this.menu = this.getChildById(this.container, 'menu');
557 this.scrollable = this.getChildById(this.container,
559 this.lineheight = this.getChildById(this.container,
562 [ this.getChildById(this.container, 'console'),
563 this.getChildById(this.container, 'alt_console') ];
564 var ieProbe = this.getChildById(this.container, 'ieprobe');
565 this.padding = this.getChildById(this.container, 'padding');
566 this.cursor = this.getChildById(this.container, 'cursor');
567 this.usercss = this.getChildById(this.container, 'usercss');
568 this.space = this.getChildById(this.container, 'space');
569 this.input = this.getChildById(this.container, 'input');
570 this.cliphelper = this.getChildById(this.container,
573 // Add any user selectable style sheets to the menu
574 this.initializeUserCSSStyles();
576 // Remember the dimensions of a standard character glyph. We would
577 // expect that we could just check cursor.clientWidth/Height at any time,
578 // but it turns out that browsers sometimes invalidate these values
579 // (e.g. while displaying a print preview screen).
580 this.cursorWidth = this.cursor.clientWidth;
581 this.cursorHeight = this.lineheight.clientHeight;
583 // IE has a slightly different boxing model, that we need to compensate for
584 this.isIE = ieProbe.offsetTop > 1;
586 this.console.innerHTML = '';
588 // Determine if the terminal window is positioned at the beginning of the
589 // page, or if it is embedded somewhere else in the page. For full-screen
590 // terminals, automatically resize whenever the browser window changes.
591 var marginTop = parseInt(this.getCurrentComputedStyle(
592 document.body, 'marginTop'));
593 var marginLeft = parseInt(this.getCurrentComputedStyle(
594 document.body, 'marginLeft'));
595 var marginRight = parseInt(this.getCurrentComputedStyle(
596 document.body, 'marginRight'));
597 var x = this.container.offsetLeft;
598 var y = this.container.offsetTop;
599 for (var parent = this.container; parent = parent.offsetParent; ) {
600 x += parent.offsetLeft;
601 y += parent.offsetTop;
603 this.isEmbedded = marginTop != y ||
605 (window.innerWidth ||
606 document.documentElement.clientWidth ||
607 document.body.clientWidth) -
608 marginRight != x + this.container.offsetWidth;
609 if (!this.isEmbedded) {
610 // Some browsers generate resize events when the terminal is first
611 // shown. Disable showing the size indicator until a little bit after
612 // the terminal has been rendered the first time.
613 this.indicateSize = false;
614 setTimeout(function(vt100) {
616 vt100.indicateSize = true;
619 this.addListener(window, 'resize',
622 vt100.hideContextMenu();
624 vt100.showCurrentSize();
628 // Hide extra scrollbars attached to window
629 document.body.style.margin = '0px';
630 try { document.body.style.overflow ='hidden'; } catch (e) { }
631 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
635 this.hideContextMenu();
637 // Add listener to reconnect button
638 this.addListener(this.reconnectBtn.firstChild, 'click',
641 var rc = vt100.reconnect();
647 // Add input listeners
648 this.addListener(this.input, 'blur',
650 return function() { vt100.blurCursor(); } }(this));
651 this.addListener(this.input, 'focus',
653 return function() { vt100.focusCursor(); } }(this));
654 this.addListener(this.input, 'keydown',
657 if (!e) e = window.event;
658 return vt100.keyDown(e); } }(this));
659 this.addListener(this.input, 'keypress',
662 if (!e) e = window.event;
663 return vt100.keyPressed(e); } }(this));
664 this.addListener(this.input, 'keyup',
667 if (!e) e = window.event;
668 return vt100.keyUp(e); } }(this));
670 // Attach listeners that move the focus to the <input> field. This way we
671 // can make sure that we can receive keyboard input.
672 var mouseEvent = function(vt100, type) {
674 if (!e) e = window.event;
675 return vt100.mouseEvent(e, type);
678 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
679 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
680 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
682 // Initialize the blank terminal window.
683 this.currentScreen = 0;
686 this.numScrollbackLines = 0;
688 this.bottom = 0x7FFFFFFF;
695 VT100.prototype.getChildById = function(parent, id) {
696 var nodeList = parent.all || parent.getElementsByTagName('*');
697 if (typeof nodeList.namedItem == 'undefined') {
698 for (var i = 0; i < nodeList.length; i++) {
699 if (nodeList[i].id == id) {
705 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
706 return elem ? elem[0] || elem : null;
710 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
711 if (typeof elem.currentStyle != 'undefined') {
712 return elem.currentStyle[style];
714 return document.defaultView.getComputedStyle(elem, null)[style];
718 VT100.prototype.reconnect = function() {
722 VT100.prototype.showReconnect = function(state) {
724 this.reconnectBtn.style.visibility = '';
726 this.reconnectBtn.style.visibility = 'hidden';
730 VT100.prototype.repairElements = function(console) {
731 for (var line = console.firstChild; line; line = line.nextSibling) {
732 if (!line.clientHeight) {
733 var newLine = document.createElement(line.tagName);
734 newLine.style.cssText = line.style.cssText;
735 newLine.className = line.className;
736 if (line.tagName == 'DIV') {
737 for (var span = line.firstChild; span; span = span.nextSibling) {
738 var newSpan = document.createElement(span.tagName);
739 newSpan.style.cssText = span.style.cssText;
740 newSpan.style.className = span.style.className;
741 this.setTextContent(newSpan, this.getTextContent(span));
742 newLine.appendChild(newSpan);
745 this.setTextContent(newLine, this.getTextContent(line));
747 line.parentNode.replaceChild(newLine, line);
753 VT100.prototype.resized = function(w, h) {
756 VT100.prototype.resizer = function() {
757 // The cursor can get corrupted if the print-preview is displayed in Firefox.
758 // Recreating it, will repair it.
759 var newCursor = document.createElement('pre');
760 this.setTextContent(newCursor, ' ');
761 newCursor.id = 'cursor';
762 newCursor.style.cssText = this.cursor.style.cssText;
763 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
764 if (!newCursor.clientHeight) {
765 // Things are broken right now. This is probably because we are
766 // displaying the print-preview. Just don't change any of our settings
767 // until the print dialog is closed again.
768 newCursor.parentNode.removeChild(newCursor);
771 // Swap the old broken cursor for the newly created one.
772 this.cursor.parentNode.removeChild(this.cursor);
773 this.cursor = newCursor;
776 // Really horrible things happen if the contents of the terminal changes
777 // while the print-preview is showing. We get HTML elements that show up
778 // in the DOM, but that do not take up any space. Find these elements and
780 this.repairElements(this.console[0]);
781 this.repairElements(this.console[1]);
783 // Lock the cursor size to the size of a normal character. This helps with
784 // characters that are taller/shorter than normal. Unfortunately, we will
785 // still get confused if somebody enters a character that is wider/narrower
786 // than normal. This can happen if the browser tries to substitute a
787 // characters from a different font.
788 this.cursor.style.width = this.cursorWidth + 'px';
789 this.cursor.style.height = this.cursorHeight + 'px';
791 // Adjust height for one pixel padding of the #vt100 element.
792 // The latter is necessary to properly display the inactive cursor.
793 var console = this.console[this.currentScreen];
794 var height = (this.isEmbedded ? this.container.clientHeight
795 : (window.innerHeight ||
796 document.documentElement.clientHeight ||
797 document.body.clientHeight))-1;
798 var partial = height % this.cursorHeight;
799 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
800 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
801 var oldTerminalHeight = this.terminalHeight;
805 // Clip the cursor to the visible screen.
806 var cx = this.cursorX;
807 var cy = this.cursorY + this.numScrollbackLines;
809 // The alternate screen never keeps a scroll back buffer.
810 this.updateNumScrollbackLines();
811 while (this.currentScreen && this.numScrollbackLines > 0) {
812 console.removeChild(console.firstChild);
813 this.numScrollbackLines--;
815 cy -= this.numScrollbackLines;
818 } else if (cx > this.terminalWidth) {
819 cx = this.terminalWidth - 1;
826 } else if (cy > this.terminalHeight) {
827 cy = this.terminalHeight - 1;
833 // Clip the scroll region to the visible screen.
834 if (this.bottom > this.terminalHeight ||
835 this.bottom == oldTerminalHeight) {
836 this.bottom = this.terminalHeight;
838 if (this.top >= this.bottom) {
839 this.top = this.bottom-1;
845 // Truncate lines, if necessary. Explicitly reposition cursor (this is
846 // particularly important after changing the screen number), and reset
847 // the scroll region to the default.
848 this.truncateLines(this.terminalWidth);
849 this.putString(cx, cy, '', undefined);
850 this.scrollable.scrollTop = this.numScrollbackLines *
851 this.cursorHeight + 1;
853 // Update classNames for lines in the scrollback buffer
854 var line = console.firstChild;
855 for (var i = 0; i < this.numScrollbackLines; i++) {
856 line.className = 'scrollback';
857 line = line.nextSibling;
861 line = line.nextSibling;
864 // Reposition the reconnect button
865 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
867 this.reconnectBtn.clientWidth)/2 + 'px';
868 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
869 this.reconnectBtn.clientHeight)/2 + 'px';
871 // Send notification that the window size has been changed
872 this.resized(this.terminalWidth, this.terminalHeight);
875 VT100.prototype.showCurrentSize = function() {
876 if (!this.indicateSize) {
879 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
881 this.curSizeBox.style.left =
882 (this.terminalWidth*this.cursorWidth/
884 this.curSizeBox.clientWidth)/2 + 'px';
885 this.curSizeBox.style.top =
886 (this.terminalHeight*this.cursorHeight -
887 this.curSizeBox.clientHeight)/2 + 'px';
888 this.curSizeBox.style.visibility = '';
889 if (this.curSizeTimeout) {
890 clearTimeout(this.curSizeTimeout);
893 // Only show the terminal size for a short amount of time after resizing.
894 // Then hide this information, again. Some browsers generate resize events
895 // throughout the entire resize operation. This is nice, and we will show
896 // the terminal size while the user is dragging the window borders.
897 // Other browsers only generate a single event when the user releases the
898 // mouse. In those cases, we can only show the terminal size once at the
899 // end of the resize operation.
900 this.curSizeTimeout = setTimeout(function(vt100) {
902 vt100.curSizeTimeout = null;
903 vt100.curSizeBox.style.visibility = 'hidden';
908 VT100.prototype.selection = function() {
910 return '' + (window.getSelection && window.getSelection() ||
911 document.selection && document.selection.type == 'Text' &&
912 document.selection.createRange().text || '');
918 VT100.prototype.cancelEvent = function(event) {
920 // For non-IE browsers
921 event.stopPropagation();
922 event.preventDefault();
927 event.cancelBubble = true;
928 event.returnValue = false;
936 VT100.prototype.mouseEvent = function(event, type) {
937 // If any text is currently selected, do not move the focus as that would
938 // invalidate the selection.
939 var selection = this.selection();
940 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
944 // Compute mouse position in characters.
945 var offsetX = this.container.offsetLeft;
946 var offsetY = this.container.offsetTop;
947 for (var e = this.container; e = e.offsetParent; ) {
948 offsetX += e.offsetLeft;
949 offsetY += e.offsetTop;
951 var x = (event.clientX - offsetX) / this.cursorWidth;
952 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
953 this.cursorHeight - this.numScrollbackLines;
955 if (x >= this.terminalWidth) {
956 x = this.terminalWidth - 1;
963 if (y >= this.terminalHeight) {
964 y = this.terminalHeight - 1;
972 // Compute button number and modifier keys.
973 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
974 typeof event.pageX != 'undefined' ? event.button :
975 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
976 if (button != undefined) {
977 if (event.shiftKey) {
980 if (event.altKey || event.metaKey) {
988 // Report mouse events if they happen inside of the current screen and
989 // with the SHIFT key unpressed. Both of these restrictions do not apply
990 // for button releases, as we always want to report those.
991 if (this.mouseReporting && !selection.length &&
992 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
993 if (inside || type != 0 /* MOUSE_DOWN */) {
994 if (button != undefined) {
995 var report = '\u001B[M' + String.fromCharCode(button + 32) +
996 String.fromCharCode(x + 33) +
997 String.fromCharCode(y + 33);
998 if (type != 2 /* MOUSE_CLICK */) {
999 this.keysPressed(report);
1002 // If we reported the event, stop propagating it (not sure, if this
1003 // actually works on most browsers; blocking the global "oncontextmenu"
1004 // even is still necessary).
1005 return this.cancelEvent(event);
1010 // Bring up context menu.
1011 if (button == 2 && !event.shiftKey) {
1012 if (type == 0 /* MOUSE_DOWN */) {
1013 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
1015 return this.cancelEvent(event);
1018 if (this.mouseReporting) {
1020 event.shiftKey = false;
1028 VT100.prototype.replaceChar = function(s, ch, repl) {
1029 for (var i = -1;;) {
1030 i = s.indexOf(ch, i + 1);
1034 s = s.substr(0, i) + repl + s.substr(i + 1);
1039 VT100.prototype.htmlEscape = function(s) {
1040 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1041 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1044 VT100.prototype.getTextContent = function(elem) {
1045 return elem.textContent ||
1046 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1049 VT100.prototype.setTextContent = function(elem, s) {
1050 // Check if we find any URLs in the text. If so, automatically convert them
1052 if (this.urlRE && this.urlRE.test(s)) {
1056 if (RegExp.leftContext != null) {
1057 inner += this.htmlEscape(RegExp.leftContext);
1058 consumed += RegExp.leftContext.length;
1060 var url = this.htmlEscape(RegExp.lastMatch);
1063 // If no protocol was specified, try to guess a reasonable one.
1064 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1065 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1066 var slash = url.indexOf('/');
1067 var at = url.indexOf('@');
1068 var question = url.indexOf('?');
1070 (at < question || question < 0) &&
1071 (slash < 0 || (question > 0 && slash > question))) {
1072 fullUrl = 'mailto:' + url;
1074 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1079 inner += '<a target="vt100Link" href="' + fullUrl +
1080 '">' + url + '</a>';
1081 consumed += RegExp.lastMatch.length;
1082 s = s.substr(consumed);
1083 if (!this.urlRE.test(s)) {
1084 if (RegExp.rightContext != null) {
1085 inner += this.htmlEscape(RegExp.rightContext);
1090 elem.innerHTML = inner;
1094 // Updating the content of an element is an expensive operation. It actually
1095 // pays off to first check whether the element is still unchanged.
1096 if (typeof elem.textContent == 'undefined') {
1097 if (elem.innerText != s) {
1101 // Very old versions of IE do not allow setting innerText. Instead,
1102 // remove all children, by setting innerHTML and then set the text
1103 // using DOM methods.
1104 elem.innerHTML = '';
1105 elem.appendChild(document.createTextNode(
1106 this.replaceChar(s, ' ', '\u00A0')));
1110 if (elem.textContent != s) {
1111 elem.textContent = s;
1116 VT100.prototype.insertBlankLine = function(y, color, style) {
1117 // Insert a blank line a position y. This method ignores the scrollback
1118 // buffer. The caller has to add the length of the scrollback buffer to
1119 // the position, if necessary.
1120 // If the position is larger than the number of current lines, this
1121 // method just adds a new line right after the last existing one. It does
1122 // not add any missing lines in between. It is the caller's responsibility
1125 color = 'ansi0 bgAnsi15';
1131 if (color != 'ansi0 bgAnsi15' && !style) {
1132 line = document.createElement('pre');
1133 this.setTextContent(line, '\n');
1135 line = document.createElement('div');
1136 var span = document.createElement('span');
1137 span.style.cssText = style;
1138 span.style.className = color;
1139 this.setTextContent(span, this.spaces(this.terminalWidth));
1140 line.appendChild(span);
1142 line.style.height = this.cursorHeight + 'px';
1143 var console = this.console[this.currentScreen];
1144 if (console.childNodes.length > y) {
1145 console.insertBefore(line, console.childNodes[y]);
1147 console.appendChild(line);
1151 VT100.prototype.updateWidth = function() {
1152 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1153 this.cursorWidth*this.scale);
1154 return this.terminalWidth;
1157 VT100.prototype.updateHeight = function() {
1158 // We want to be able to display either a terminal window that fills the
1159 // entire browser window, or a terminal window that is contained in a
1160 // <div> which is embededded somewhere in the web page.
1161 if (this.isEmbedded) {
1162 // Embedded terminal. Use size of the containing <div> (id="vt100").
1163 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1166 // Use the full browser window.
1167 this.terminalHeight = Math.floor(((window.innerHeight ||
1168 document.documentElement.clientHeight ||
1169 document.body.clientHeight)-1)/
1172 return this.terminalHeight;
1175 VT100.prototype.updateNumScrollbackLines = function() {
1176 var scrollback = Math.floor(
1177 this.console[this.currentScreen].offsetHeight /
1178 this.cursorHeight) -
1179 this.terminalHeight;
1180 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1181 return this.numScrollbackLines;
1184 VT100.prototype.truncateLines = function(width) {
1188 for (var line = this.console[this.currentScreen].firstChild; line;
1189 line = line.nextSibling) {
1190 if (line.tagName == 'DIV') {
1193 // Traverse current line and truncate it once we saw "width" characters
1194 for (var span = line.firstChild; span;
1195 span = span.nextSibling) {
1196 var s = this.getTextContent(span);
1198 if (x + l > width) {
1199 this.setTextContent(span, s.substr(0, width - x));
1200 while (span.nextSibling) {
1201 line.removeChild(line.lastChild);
1207 // Prune white space from the end of the current line
1208 var span = line.lastChild;
1210 span.className == 'ansi0 bgAnsi15' &&
1211 !span.style.cssText.length) {
1212 // Scan backwards looking for first non-space character
1213 var s = this.getTextContent(span);
1214 for (var i = s.length; i--; ) {
1215 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1216 if (i+1 != s.length) {
1217 this.setTextContent(s.substr(0, i+1));
1225 span = span.previousSibling;
1227 // Remove blank <span>'s from end of line
1228 line.removeChild(sibling);
1230 // Remove entire line (i.e. <div>), if empty
1231 var blank = document.createElement('pre');
1232 blank.style.height = this.cursorHeight + 'px';
1233 this.setTextContent(blank, '\n');
1234 line.parentNode.replaceChild(blank, line);
1242 VT100.prototype.putString = function(x, y, text, color, style) {
1244 color = 'ansi0 bgAnsi15';
1249 var yIdx = y + this.numScrollbackLines;
1255 var console = this.console[this.currentScreen];
1256 if (!text.length && (yIdx >= console.childNodes.length ||
1257 console.childNodes[yIdx].tagName != 'DIV')) {
1258 // Positioning cursor to a blank location
1261 // Create missing blank lines at end of page
1262 while (console.childNodes.length <= yIdx) {
1263 // In order to simplify lookups, we want to make sure that each line
1264 // is represented by exactly one element (and possibly a whole bunch of
1266 // For non-blank lines, we can create a <div> containing one or more
1267 // <span>s. For blank lines, this fails as browsers tend to optimize them
1268 // away. But fortunately, a <pre> tag containing a newline character
1269 // appears to work for all browsers (a would also work, but then
1270 // copying from the browser window would insert superfluous spaces into
1272 this.insertBlankLine(yIdx);
1274 line = console.childNodes[yIdx];
1276 // If necessary, promote blank '\n' line to a <div> tag
1277 if (line.tagName != 'DIV') {
1278 var div = document.createElement('div');
1279 div.style.height = this.cursorHeight + 'px';
1280 div.innerHTML = '<span></span>';
1281 console.replaceChild(div, line);
1285 // Scan through list of <span>'s until we find the one where our text
1287 span = line.firstChild;
1289 while (span.nextSibling && xPos < x) {
1290 len = this.getTextContent(span).length;
1291 if (xPos + len > x) {
1295 span = span.nextSibling;
1299 // If current <span> is not long enough, pad with spaces or add new
1301 s = this.getTextContent(span);
1302 var oldColor = span.className;
1303 var oldStyle = span.style.cssText;
1304 if (xPos + s.length < x) {
1305 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1306 span = document.createElement('span');
1307 line.appendChild(span);
1308 span.className = 'ansi0 bgAnsi15';
1309 span.style.cssText = '';
1310 oldColor = 'ansi0 bgAnsi15';
1317 } while (xPos + s.length < x);
1320 // If styles do not match, create a new <span>
1321 var del = text.length - s.length + x - xPos;
1322 if (oldColor != color ||
1323 (oldStyle != style && (oldStyle || style))) {
1325 // Replacing text at beginning of existing <span>
1326 if (text.length >= s.length) {
1327 // New text is equal or longer than existing text
1330 // Insert new <span> before the current one, then remove leading
1331 // part of existing <span>, adjust style of new <span>, and finally
1333 sibling = document.createElement('span');
1334 line.insertBefore(sibling, span);
1335 this.setTextContent(span, s.substr(text.length));
1340 // Replacing text some way into the existing <span>
1341 var remainder = s.substr(x + text.length - xPos);
1342 this.setTextContent(span, s.substr(0, x - xPos));
1344 sibling = document.createElement('span');
1345 if (span.nextSibling) {
1346 line.insertBefore(sibling, span.nextSibling);
1348 if (remainder.length) {
1349 sibling = document.createElement('span');
1350 sibling.className = oldColor;
1351 sibling.style.cssText = oldStyle;
1352 this.setTextContent(sibling, remainder);
1353 line.insertBefore(sibling, span.nextSibling);
1356 line.appendChild(sibling);
1358 if (remainder.length) {
1359 sibling = document.createElement('span');
1360 sibling.className = oldColor;
1361 sibling.style.cssText = oldStyle;
1362 this.setTextContent(sibling, remainder);
1363 line.appendChild(sibling);
1368 span.className = color;
1369 span.style.cssText = style;
1371 // Overwrite (partial) <span> with new text
1372 s = s.substr(0, x - xPos) +
1374 s.substr(x + text.length - xPos);
1376 this.setTextContent(span, s);
1379 // Delete all subsequent <span>'s that have just been overwritten
1380 sibling = span.nextSibling;
1381 while (del > 0 && sibling) {
1382 s = this.getTextContent(sibling);
1385 line.removeChild(sibling);
1387 sibling = span.nextSibling;
1389 this.setTextContent(sibling, s.substr(del));
1394 // Merge <span> with next sibling, if styles are identical
1395 if (sibling && span.className == sibling.className &&
1396 span.style.cssText == sibling.style.cssText) {
1397 this.setTextContent(span,
1398 this.getTextContent(span) +
1399 this.getTextContent(sibling));
1400 line.removeChild(sibling);
1406 this.cursorX = x + text.length;
1407 if (this.cursorX >= this.terminalWidth) {
1408 this.cursorX = this.terminalWidth - 1;
1409 if (this.cursorX < 0) {
1415 if (!this.cursor.style.visibility) {
1416 var idx = this.cursorX - xPos;
1418 // If we are in a non-empty line, take the cursor Y position from the
1419 // other elements in this line. If dealing with broken, non-proportional
1420 // fonts, this is likely to yield better results.
1421 pixelY = span.offsetTop +
1422 span.offsetParent.offsetTop;
1423 s = this.getTextContent(span);
1424 var nxtIdx = idx - s.length;
1426 this.setTextContent(this.cursor, s.charAt(idx));
1427 pixelX = span.offsetLeft +
1428 idx*span.offsetWidth / s.length;
1431 pixelX = span.offsetLeft + span.offsetWidth;
1433 if (span.nextSibling) {
1434 s = this.getTextContent(span.nextSibling);
1435 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1437 pixelX = span.nextSibling.offsetLeft +
1438 nxtIdx*span.offsetWidth / s.length;
1441 this.setTextContent(this.cursor, ' ');
1445 this.setTextContent(this.cursor, ' ');
1449 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
1452 this.setTextContent(this.space, this.spaces(this.cursorX));
1453 this.cursor.style.left = (this.space.offsetWidth +
1454 console.offsetLeft)/this.scale + 'px';
1456 this.cursorY = yIdx - this.numScrollbackLines;
1458 this.cursor.style.top = pixelY + 'px';
1460 this.cursor.style.top = yIdx*this.cursorHeight +
1461 console.offsetTop + 'px';
1465 // Merge <span> with previous sibling, if styles are identical
1466 if ((sibling = span.previousSibling) &&
1467 span.className == sibling.className &&
1468 span.style.cssText == sibling.style.cssText) {
1469 this.setTextContent(span,
1470 this.getTextContent(sibling) +
1471 this.getTextContent(span));
1472 line.removeChild(sibling);
1475 // Prune white space from the end of the current line
1476 span = line.lastChild;
1478 span.className == 'ansi0 bgAnsi15' &&
1479 !span.style.cssText.length) {
1480 // Scan backwards looking for first non-space character
1481 s = this.getTextContent(span);
1482 for (var i = s.length; i--; ) {
1483 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1484 if (i+1 != s.length) {
1485 this.setTextContent(s.substr(0, i+1));
1493 span = span.previousSibling;
1495 // Remove blank <span>'s from end of line
1496 line.removeChild(sibling);
1498 // Remove entire line (i.e. <div>), if empty
1499 var blank = document.createElement('pre');
1500 blank.style.height = this.cursorHeight + 'px';
1501 this.setTextContent(blank, '\n');
1502 line.parentNode.replaceChild(blank, line);
1509 VT100.prototype.gotoXY = function(x, y) {
1510 if (x >= this.terminalWidth) {
1511 x = this.terminalWidth - 1;
1517 if (this.offsetMode) {
1522 maxY = this.terminalHeight;
1530 this.putString(x, y, '', undefined);
1531 this.needWrap = false;
1534 VT100.prototype.gotoXaY = function(x, y) {
1535 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1538 VT100.prototype.refreshInvertedState = function() {
1539 if (this.isInverted) {
1540 this.scrollable.className += ' inverted';
1542 this.scrollable.className = this.scrollable.className.
1543 replace(/ *inverted/, '');
1547 VT100.prototype.enableAlternateScreen = function(state) {
1548 // Don't do anything, if we are already on the desired screen
1549 if ((state ? 1 : 0) == this.currentScreen) {
1550 // Calling the resizer is not actually necessary. But it is a good way
1551 // of resetting state that might have gotten corrupted.
1556 // We save the full state of the normal screen, when we switch away from it.
1557 // But for the alternate screen, no saving is necessary. We always reset
1558 // it when we switch to it.
1563 // Display new screen, and initialize state (the resizer does that for us).
1564 this.currentScreen = state ? 1 : 0;
1565 this.console[1-this.currentScreen].style.display = 'none';
1566 this.console[this.currentScreen].style.display = '';
1568 // Select appropriate character pitch.
1569 var styles = [ 'transform',
1573 for (var i = 0; i < styles.length; ++i) {
1574 if (typeof this.console[0].style[styles[i]] != 'undefined') {
1576 // Upon enabling the alternate screen, we switch to 80 column mode. But
1577 // upon returning to the regular screen, we restore the mode that was
1578 // in effect previously.
1579 this.console[1].style[styles[i]] = '';
1582 this.console[this.currentScreen].style[styles[i]];
1583 this.cursor.style[styles[i]] = style;
1584 this.space.style[styles[i]] = style;
1585 this.scale = style == '' ? 1.0:1.65;
1586 if (styles[i] == 'filter') {
1587 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
1594 // If we switched to the alternate screen, reset it completely. Otherwise,
1595 // restore the saved state.
1598 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1600 this.restoreCursor();
1604 VT100.prototype.hideCursor = function() {
1605 var hidden = this.cursor.style.visibility == 'hidden';
1607 this.cursor.style.visibility = 'hidden';
1613 VT100.prototype.showCursor = function(x, y) {
1614 if (this.cursor.style.visibility) {
1615 this.cursor.style.visibility = '';
1616 this.putString(x == undefined ? this.cursorX : x,
1617 y == undefined ? this.cursorY : y,
1624 VT100.prototype.scrollBack = function() {
1625 var i = this.scrollable.scrollTop -
1626 this.scrollable.clientHeight;
1627 this.scrollable.scrollTop = i < 0 ? 0 : i;
1630 VT100.prototype.scrollFore = function() {
1631 var i = this.scrollable.scrollTop +
1632 this.scrollable.clientHeight;
1633 this.scrollable.scrollTop = i > this.numScrollbackLines *
1634 this.cursorHeight + 1
1635 ? this.numScrollbackLines *
1636 this.cursorHeight + 1
1640 VT100.prototype.spaces = function(i) {
1648 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
1653 if (w > this.terminalWidth) {
1654 w = this.terminalWidth;
1656 if ((w -= x) <= 0) {
1663 if (h > this.terminalHeight) {
1664 h = this.terminalHeight;
1666 if ((h -= y) <= 0) {
1670 // Special case the situation where we clear the entire screen, and we do
1671 // not have a scrollback buffer. In that case, we should just remove all
1673 if (!this.numScrollbackLines &&
1674 w == this.terminalWidth && h == this.terminalHeight &&
1675 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
1676 var console = this.console[this.currentScreen];
1677 while (console.lastChild) {
1678 console.removeChild(console.lastChild);
1680 this.putString(this.cursorX, this.cursorY, '', undefined);
1682 var hidden = this.hideCursor();
1683 var cx = this.cursorX;
1684 var cy = this.cursorY;
1685 var s = this.spaces(w);
1686 for (var i = y+h; i-- > y; ) {
1687 this.putString(x, i, s, color, style);
1689 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1693 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1695 var className = [ ];
1697 var console = this.console[this.currentScreen];
1698 if (sY >= console.childNodes.length) {
1699 text[0] = this.spaces(w);
1700 className[0] = undefined;
1701 style[0] = undefined;
1703 var line = console.childNodes[sY];
1704 if (line.tagName != 'DIV' || !line.childNodes.length) {
1705 text[0] = this.spaces(w);
1706 className[0] = undefined;
1707 style[0] = undefined;
1710 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1711 var s = this.getTextContent(span);
1714 var o = sX > x ? sX - x : 0;
1715 text[text.length] = s.substr(o, w);
1716 className[className.length] = span.className;
1717 style[style.length] = span.style.cssText;
1723 text[text.length] = this.spaces(w);
1724 className[className.length] = undefined;
1725 style[style.length] = undefined;
1729 var hidden = this.hideCursor();
1730 var cx = this.cursorX;
1731 var cy = this.cursorY;
1732 for (var i = 0; i < text.length; i++) {
1735 color = className[i];
1737 color = 'ansi0 bgAnsi15';
1739 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
1740 dX += text[i].length;
1742 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1745 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
1747 var left = incX < 0 ? -incX : 0;
1748 var right = incX > 0 ? incX : 0;
1749 var up = incY < 0 ? -incY : 0;
1750 var down = incY > 0 ? incY : 0;
1752 // Clip region against terminal size
1753 var dontScroll = null;
1758 if (w > this.terminalWidth - right) {
1759 w = this.terminalWidth - right;
1761 if ((w -= x) <= 0) {
1768 if (h > this.terminalHeight - down) {
1769 h = this.terminalHeight - down;
1775 if (style && style.indexOf('underline')) {
1776 // Different terminal emulators disagree on the attributes that
1777 // are used for scrolling. The consensus seems to be, never to
1778 // fill with underlined spaces. N.B. this is different from the
1779 // cases when the user blanks a region. User-initiated blanking
1780 // always fills with all of the current attributes.
1781 style = style.replace(/text-decoration:underline;/, '');
1784 // Compute current scroll position
1785 var scrollPos = this.numScrollbackLines -
1786 (this.scrollable.scrollTop-1) / this.cursorHeight;
1788 // Determine original cursor position. Hide cursor temporarily to avoid
1789 // visual artifacts.
1790 var hidden = this.hideCursor();
1791 var cx = this.cursorX;
1792 var cy = this.cursorY;
1793 var console = this.console[this.currentScreen];
1795 if (!incX && !x && w == this.terminalWidth) {
1796 // Scrolling entire lines
1799 if (!this.currentScreen && y == -incY &&
1800 h == this.terminalHeight + incY) {
1801 // Scrolling up with adding to the scrollback buffer. This is only
1802 // possible if there are at least as many lines in the console,
1803 // as the terminal is high
1804 while (console.childNodes.length < this.terminalHeight) {
1805 this.insertBlankLine(this.terminalHeight);
1808 // Add new lines at bottom in order to force scrolling
1809 for (var i = 0; i < y; i++) {
1810 this.insertBlankLine(console.childNodes.length, color, style);
1813 // Adjust the number of lines in the scrollback buffer by
1814 // removing excess entries.
1815 this.updateNumScrollbackLines();
1816 while (this.numScrollbackLines >
1817 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1818 console.removeChild(console.firstChild);
1819 this.numScrollbackLines--;
1822 // Mark lines in the scrollback buffer, so that they do not get
1824 for (var i = this.numScrollbackLines, j = -incY;
1825 i-- > 0 && j-- > 0; ) {
1826 console.childNodes[i].className = 'scrollback';
1829 // Scrolling up without adding to the scrollback buffer.
1832 console.childNodes.length >
1833 this.numScrollbackLines + y + incY; ) {
1834 console.removeChild(console.childNodes[
1835 this.numScrollbackLines + y + incY]);
1838 // If we used to have a scrollback buffer, then we must make sure
1839 // that we add back blank lines at the bottom of the terminal.
1840 // Similarly, if we are scrolling in the middle of the screen,
1841 // we must add blank lines to ensure that the bottom of the screen
1842 // does not move up.
1843 if (this.numScrollbackLines > 0 ||
1844 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1845 for (var i = -incY; i-- > 0; ) {
1846 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1855 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1856 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1858 for (var i = incY; i--; ) {
1859 this.insertBlankLine(this.numScrollbackLines + y, color, style);
1863 // Scrolling partial lines
1865 // Scrolling up or horizontally within a line
1866 for (var i = y + this.numScrollbackLines;
1867 i < y + this.numScrollbackLines + h;
1869 this.copyLineSegment(x + incX, i + incY, x, i, w);
1873 for (var i = y + this.numScrollbackLines + h;
1874 i-- > y + this.numScrollbackLines; ) {
1875 this.copyLineSegment(x + incX, i + incY, x, i, w);
1879 // Clear blank regions
1881 this.clearRegion(x, y, incX, h, color, style);
1882 } else if (incX < 0) {
1883 this.clearRegion(x + w + incX, y, -incX, h, color, style);
1886 this.clearRegion(x, y, w, incY, color, style);
1887 } else if (incY < 0) {
1888 this.clearRegion(x, y + h + incY, w, -incY, color, style);
1892 // Reset scroll position
1893 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1894 this.cursorHeight + 1;
1896 // Move cursor back to its original position
1897 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1901 VT100.prototype.copy = function(selection) {
1902 if (selection == undefined) {
1903 selection = this.selection();
1905 this.internalClipboard = undefined;
1906 if (selection.length) {
1909 this.cliphelper.value = selection;
1910 this.cliphelper.select();
1911 this.cliphelper.createTextRange().execCommand('copy');
1913 this.internalClipboard = selection;
1915 this.cliphelper.value = '';
1919 VT100.prototype.copyLast = function() {
1920 // Opening the context menu can remove the selection. We try to prevent this
1921 // from happening, but that is not possible for all browsers. So, instead,
1922 // we compute the selection before showing the menu.
1923 this.copy(this.lastSelection);
1926 VT100.prototype.pasteFnc = function() {
1927 var clipboard = undefined;
1928 if (this.internalClipboard != undefined) {
1929 clipboard = this.internalClipboard;
1932 this.cliphelper.value = '';
1933 this.cliphelper.createTextRange().execCommand('paste');
1934 clipboard = this.cliphelper.value;
1938 this.cliphelper.value = '';
1939 if (clipboard && this.menu.style.visibility == 'hidden') {
1941 this.keysPressed('' + clipboard);
1948 VT100.prototype.toggleUTF = function() {
1949 this.utfEnabled = !this.utfEnabled;
1951 // We always persist the last value that the user selected. Not necessarily
1952 // the last value that a random program requested.
1953 this.utfPreferred = this.utfEnabled;
1956 VT100.prototype.toggleBell = function() {
1957 this.visualBell = !this.visualBell;
1960 VT100.prototype.toggleCursorBlinking = function() {
1961 this.blinkingCursor = !this.blinkingCursor;
1964 VT100.prototype.about = function() {
1965 alert("VT100 Terminal Emulator " + "2.10 (revision 215)" +
1966 "\nCopyright 2008-2010 by Markus Gutschke\n" +
1967 "For more information check http://shellinabox.com");
1970 VT100.prototype.hideContextMenu = function() {
1971 this.menu.style.visibility = 'hidden';
1972 this.menu.style.top = '-100px';
1973 this.menu.style.left = '-100px';
1974 this.menu.style.width = '0px';
1975 this.menu.style.height = '0px';
1978 VT100.prototype.extendContextMenu = function(entries, actions) {
1981 VT100.prototype.showContextMenu = function(x, y) {
1982 this.menu.innerHTML =
1983 '<table class="popup" ' +
1984 'cellpadding="0" cellspacing="0">' +
1986 '<ul id="menuentries">' +
1987 '<li id="beginclipboard">Copy</li>' +
1988 '<li id="endclipboard">Paste</li>' +
1990 '<li id="reset">Reset</li>' +
1992 '<li id="beginconfig">' +
1993 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
1996 (this.visualBell ? '<img src="enabled.gif" />' : '') +
1998 '<li id="endconfig">' +
1999 (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2000 'Blinking Cursor</li>'+
2001 (this.usercss.firstChild ?
2002 '<hr id="beginusercss" />' +
2003 this.usercss.innerHTML +
2004 '<hr id="endusercss" />' :
2006 '<li id="about">About...</li>' +
2011 var popup = this.menu.firstChild;
2012 var menuentries = this.getChildById(popup, 'menuentries');
2014 // Determine menu entries that should be disabled
2015 this.lastSelection = this.selection();
2016 if (!this.lastSelection.length) {
2017 menuentries.firstChild.className
2020 var p = this.pasteFnc();
2022 menuentries.childNodes[1].className
2026 // Actions for default items
2027 var actions = [ this.copyLast, p, this.reset,
2028 this.toggleUTF, this.toggleBell,
2029 this.toggleCursorBlinking ];
2031 // Actions for user CSS styles (if any)
2032 for (var i = 0; i < this.usercssActions.length; ++i) {
2033 actions[actions.length] = this.usercssActions[i];
2035 actions[actions.length] = this.about;
2037 // Allow subclasses to dynamically add entries to the context menu
2038 this.extendContextMenu(menuentries, actions);
2040 // Hook up event listeners
2041 for (var node = menuentries.firstChild, i = 0; node;
2042 node = node.nextSibling) {
2043 if (node.tagName == 'LI') {
2044 if (node.className != 'disabled') {
2045 this.addListener(node, 'mouseover',
2046 function(vt100, node) {
2048 node.className = 'hover';
2051 this.addListener(node, 'mouseout',
2052 function(vt100, node) {
2054 node.className = '';
2057 this.addListener(node, 'mousedown',
2058 function(vt100, action) {
2059 return function(event) {
2060 vt100.hideContextMenu();
2062 vt100.storeUserSettings();
2063 return vt100.cancelEvent(event || window.event);
2065 }(this, actions[i]));
2066 this.addListener(node, 'mouseup',
2068 return function(event) {
2069 return vt100.cancelEvent(event || window.event);
2072 this.addListener(node, 'mouseclick',
2074 return function(event) {
2075 return vt100.cancelEvent(event || window.event);
2083 // Position menu next to the mouse pointer
2084 if (x + popup.clientWidth > this.container.offsetWidth) {
2085 x = this.container.offsetWidth - popup.clientWidth;
2090 if (y + popup.clientHeight > this.container.offsetHeight) {
2091 y = this.container.offsetHeight-popup.clientHeight;
2096 popup.style.left = x + 'px';
2097 popup.style.top = y + 'px';
2099 // Block all other interactions with the terminal emulator
2100 this.menu.style.left = '0px';
2101 this.menu.style.top = '0px';
2102 this.menu.style.width = this.container.offsetWidth + 'px';
2103 this.menu.style.height = this.container.offsetHeight + 'px';
2104 this.addListener(this.menu, 'click', function(vt100) {
2106 vt100.hideContextMenu();
2111 this.menu.style.visibility = '';
2114 VT100.prototype.keysPressed = function(ch) {
2115 for (var i = 0; i < ch.length; i++) {
2116 var c = ch.charCodeAt(i);
2117 this.vt100(c >= 7 && c <= 15 ||
2118 c == 24 || c == 26 || c == 27 || c >= 32
2119 ? String.fromCharCode(c) : '<' + c + '>');
2123 VT100.prototype.applyModifiers = function(ch, event) {
2125 if (event.ctrlKey) {
2126 if (ch >= 32 && ch <= 127) {
2127 // For historic reasons, some control characters are treated specially
2129 case /* 3 */ 51: ch = 27; break;
2130 case /* 4 */ 52: ch = 28; break;
2131 case /* 5 */ 53: ch = 29; break;
2132 case /* 6 */ 54: ch = 30; break;
2133 case /* 7 */ 55: ch = 31; break;
2134 case /* 8 */ 56: ch = 127; break;
2135 case /* ? */ 63: ch = 127; break;
2136 default: ch &= 31; break;
2140 return String.fromCharCode(ch);
2146 VT100.prototype.handleKey = function(event) {
2147 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2148 // (event.shiftKey || event.ctrlKey || event.altKey ||
2149 // event.metaKey ? ', ' +
2150 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2151 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2154 if (typeof event.charCode != 'undefined') {
2155 // non-IE keypress events have a translated charCode value. Also, our
2156 // fake events generated when receiving keydown events include this data
2158 ch = event.charCode;
2159 key = event.keyCode;
2161 // When sending a keypress event, IE includes the translated character
2162 // code in the keyCode field.
2167 // Apply modifier keys (ctrl and shift)
2171 ch = this.applyModifiers(ch, event);
2173 // By this point, "ch" is either defined and contains the character code, or
2174 // it is undefined and "key" defines the code of a function key
2175 if (ch != undefined) {
2176 this.scrollable.scrollTop = this.numScrollbackLines *
2177 this.cursorHeight + 1;
2179 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2180 // Many programs have difficulties dealing with parametrized escape
2181 // sequences for function keys. Thus, if ALT is the only modifier
2182 // key, return Emacs-style keycodes for commonly used keys.
2184 case 33: /* Page Up */ ch = '\u001B<'; break;
2185 case 34: /* Page Down */ ch = '\u001B>'; break;
2186 case 37: /* Left */ ch = '\u001Bb'; break;
2187 case 38: /* Up */ ch = '\u001Bp'; break;
2188 case 39: /* Right */ ch = '\u001Bf'; break;
2189 case 40: /* Down */ ch = '\u001Bn'; break;
2190 case 46: /* Delete */ ch = '\u001Bd'; break;
2193 } else if (event.shiftKey && !event.ctrlKey &&
2194 !event.altKey && !event.metaKey) {
2196 case 33: /* Page Up */ this.scrollBack(); return;
2197 case 34: /* Page Down */ this.scrollFore(); return;
2201 if (ch == undefined) {
2203 case 8: /* Backspace */ ch = '\u007f'; break;
2204 case 9: /* Tab */ ch = '\u0009'; break;
2205 case 10: /* Return */ ch = '\u000A'; break;
2206 case 13: /* Enter */ ch = this.crLfMode ?
2207 '\r\n' : '\r'; break;
2208 case 16: /* Shift */ return;
2209 case 17: /* Ctrl */ return;
2210 case 18: /* Alt */ return;
2211 case 19: /* Break */ return;
2212 case 20: /* Caps Lock */ return;
2213 case 27: /* Escape */ ch = '\u001B'; break;
2214 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2215 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2216 case 35: /* End */ ch = '\u001BOF'; break;
2217 case 36: /* Home */ ch = '\u001BOH'; break;
2218 case 37: /* Left */ ch = this.cursorKeyMode ?
2219 '\u001BOD' : '\u001B[D'; break;
2220 case 38: /* Up */ ch = this.cursorKeyMode ?
2221 '\u001BOA' : '\u001B[A'; break;
2222 case 39: /* Right */ ch = this.cursorKeyMode ?
2223 '\u001BOC' : '\u001B[C'; break;
2224 case 40: /* Down */ ch = this.cursorKeyMode ?
2225 '\u001BOB' : '\u001B[B'; break;
2226 case 45: /* Insert */ ch = '\u001B[2~'; break;
2227 case 46: /* Delete */ ch = '\u001B[3~'; break;
2228 case 91: /* Left Window */ return;
2229 case 92: /* Right Window */ return;
2230 case 93: /* Select */ return;
2231 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2232 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2233 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2234 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2235 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2236 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2237 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2238 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2239 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2240 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2241 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2242 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2243 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2244 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2245 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2246 case 112: /* F1 */ ch = '\u001BOP'; break;
2247 case 113: /* F2 */ ch = '\u001BOQ'; break;
2248 case 114: /* F3 */ ch = '\u001BOR'; break;
2249 case 115: /* F4 */ ch = '\u001BOS'; break;
2250 case 116: /* F5 */ ch = '\u001B[15~'; break;
2251 case 117: /* F6 */ ch = '\u001B[17~'; break;
2252 case 118: /* F7 */ ch = '\u001B[18~'; break;
2253 case 119: /* F8 */ ch = '\u001B[19~'; break;
2254 case 120: /* F9 */ ch = '\u001B[20~'; break;
2255 case 121: /* F10 */ ch = '\u001B[21~'; break;
2256 case 122: /* F11 */ ch = '\u001B[23~'; break;
2257 case 123: /* F12 */ ch = '\u001B[24~'; break;
2258 case 144: /* Num Lock */ return;
2259 case 145: /* Scroll Lock */ return;
2260 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2261 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2262 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2263 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2264 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2265 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2266 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2267 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2268 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2269 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2270 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2273 this.scrollable.scrollTop = this.numScrollbackLines *
2274 this.cursorHeight + 1;
2278 // "ch" now contains the sequence of keycodes to send. But we might still
2279 // have to apply the effects of modifier keys.
2280 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2281 var start, digit, part1, part2;
2282 if ((start = ch.substr(0, 2)) == '\u001B[') {
2284 part1.length < ch.length &&
2285 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2286 part1 = ch.substr(0, part1.length + 1);
2288 part2 = ch.substr(part1.length);
2289 if (part1.length > 2) {
2292 } else if (start == '\u001BO') {
2294 part2 = ch.substr(2);
2296 if (part1 != undefined) {
2298 ((event.shiftKey ? 1 : 0) +
2299 (event.altKey|event.metaKey ? 2 : 0) +
2300 (event.ctrlKey ? 4 : 0)) +
2302 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2307 if (this.menu.style.visibility == 'hidden') {
2308 // this.vt100('R: c=');
2309 // for (var i = 0; i < ch.length; i++)
2310 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2311 // this.vt100('\r\n');
2312 this.keysPressed(ch);
2316 VT100.prototype.inspect = function(o, d) {
2317 if (d == undefined) {
2321 if (typeof o == 'object' && ++d < 2) {
2324 rc += this.spaces(d * 2) + i + ' -> ';
2326 rc += this.inspect(o[i], d);
2328 rc += '?' + '?' + '?\r\n';
2333 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2338 VT100.prototype.checkComposedKeys = function(event) {
2339 // Composed keys (at least on Linux) do not generate normal events.
2340 // Instead, they get entered into the text field. We normally catch
2341 // this on the next keyup event.
2342 var s = this.input.value;
2344 this.input.value = '';
2345 if (this.menu.style.visibility == 'hidden') {
2346 this.keysPressed(s);
2351 VT100.prototype.fixEvent = function(event) {
2352 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2353 // is used as a second-level selector, clear the modifier bits before
2354 // handling the event.
2355 if (event.ctrlKey && event.altKey) {
2357 fake.charCode = event.charCode;
2358 fake.keyCode = event.keyCode;
2359 fake.ctrlKey = false;
2360 fake.shiftKey = event.shiftKey;
2361 fake.altKey = false;
2362 fake.metaKey = event.metaKey;
2366 // Some browsers fail to translate keys, if both shift and alt/meta is
2367 // pressed at the same time. We try to translate those cases, but that
2368 // only works for US keyboard layouts.
2369 if (event.shiftKey) {
2372 switch (this.lastNormalKeyDownEvent.keyCode) {
2373 case 39: /* ' -> " */ u = 39; s = 34; break;
2374 case 44: /* , -> < */ u = 44; s = 60; break;
2375 case 45: /* - -> _ */ u = 45; s = 95; break;
2376 case 46: /* . -> > */ u = 46; s = 62; break;
2377 case 47: /* / -> ? */ u = 47; s = 63; break;
2379 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2380 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2381 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2382 case 51: /* 3 -> # */ u = 51; s = 35; break;
2383 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2384 case 53: /* 5 -> % */ u = 53; s = 37; break;
2385 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2386 case 55: /* 7 -> & */ u = 55; s = 38; break;
2387 case 56: /* 8 -> * */ u = 56; s = 42; break;
2388 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2390 case 59: /* ; -> : */ u = 59; s = 58; break;
2391 case 61: /* = -> + */ u = 61; s = 43; break;
2392 case 91: /* [ -> { */ u = 91; s = 123; break;
2393 case 92: /* \ -> | */ u = 92; s = 124; break;
2394 case 93: /* ] -> } */ u = 93; s = 125; break;
2395 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2397 case 109: /* - -> _ */ u = 45; s = 95; break;
2398 case 111: /* / -> ? */ u = 47; s = 63; break;
2400 case 186: /* ; -> : */ u = 59; s = 58; break;
2401 case 187: /* = -> + */ u = 61; s = 43; break;
2402 case 188: /* , -> < */ u = 44; s = 60; break;
2403 case 189: /* - -> _ */ u = 45; s = 95; break;
2404 case 190: /* . -> > */ u = 46; s = 62; break;
2405 case 191: /* / -> ? */ u = 47; s = 63; break;
2406 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2407 case 219: /* [ -> { */ u = 91; s = 123; break;
2408 case 220: /* \ -> | */ u = 92; s = 124; break;
2409 case 221: /* ] -> } */ u = 93; s = 125; break;
2410 case 222: /* ' -> " */ u = 39; s = 34; break;
2413 if (s && (event.charCode == u || event.charCode == 0)) {
2416 fake.keyCode = event.keyCode;
2417 fake.ctrlKey = event.ctrlKey;
2418 fake.shiftKey = event.shiftKey;
2419 fake.altKey = event.altKey;
2420 fake.metaKey = event.metaKey;
2427 VT100.prototype.keyDown = function(event) {
2428 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2429 // (event.shiftKey || event.ctrlKey || event.altKey ||
2430 // event.metaKey ? ', ' +
2431 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2432 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2434 this.checkComposedKeys(event);
2435 this.lastKeyPressedEvent = undefined;
2436 this.lastKeyDownEvent = undefined;
2437 this.lastNormalKeyDownEvent = event;
2440 event.keyCode == 32 ||
2441 event.keyCode >= 48 && event.keyCode <= 57 ||
2442 event.keyCode >= 65 && event.keyCode <= 90;
2445 event.keyCode >= 96 && event.keyCode <= 105 ||
2446 event.keyCode == 226;
2449 event.keyCode == 59 || event.keyCode == 61 ||
2450 event.keyCode == 106 || event.keyCode == 107 ||
2451 event.keyCode >= 109 && event.keyCode <= 111 ||
2452 event.keyCode >= 186 && event.keyCode <= 192 ||
2453 event.keyCode >= 219 && event.keyCode <= 223 ||
2454 event.keyCode == 252;
2456 if (navigator.appName == 'Konqueror') {
2457 normalKey |= event.keyCode < 128;
2462 // We normally prefer to look at keypress events, as they perform the
2463 // translation from keyCode to charCode. This is important, as the
2464 // translation is locale-dependent.
2465 // But for some keys, we must intercept them during the keydown event,
2466 // as they would otherwise get interpreted by the browser.
2467 // Even, when doing all of this, there are some keys that we can never
2468 // intercept. This applies to some of the menu navigation keys in IE.
2469 // In fact, we see them, but we cannot stop IE from seeing them, too.
2470 if ((event.charCode || event.keyCode) &&
2471 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2473 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2474 // interpret this sequence ourselves, as some keyboard layouts use
2475 // it for second-level layouts.
2476 !(event.ctrlKey && event.altKey)) ||
2477 this.catchModifiersEarly && normalKey && !alphNumKey &&
2478 (event.ctrlKey || event.altKey || event.metaKey) ||
2480 this.lastKeyDownEvent = event;
2482 fake.ctrlKey = event.ctrlKey;
2483 fake.shiftKey = event.shiftKey;
2484 fake.altKey = event.altKey;
2485 fake.metaKey = event.metaKey;
2487 fake.charCode = event.keyCode;
2491 fake.keyCode = event.keyCode;
2492 if (!alphNumKey && event.shiftKey) {
2493 fake = this.fixEvent(fake);
2497 this.handleKey(fake);
2498 this.lastNormalKeyDownEvent = undefined;
2501 // For non-IE browsers
2502 event.stopPropagation();
2503 event.preventDefault();
2508 event.cancelBubble = true;
2509 event.returnValue = false;
2519 VT100.prototype.keyPressed = function(event) {
2520 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2521 // (event.shiftKey || event.ctrlKey || event.altKey ||
2522 // event.metaKey ? ', ' +
2523 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2524 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2526 if (this.lastKeyDownEvent) {
2527 // If we already processed the key on keydown, do not process it
2528 // again here. Ideally, the browser should not even have generated a
2529 // keypress event in this case. But that does not appear to always work.
2530 this.lastKeyDownEvent = undefined;
2532 this.handleKey(event.altKey || event.metaKey
2533 ? this.fixEvent(event) : event);
2537 // For non-IE browsers
2538 event.preventDefault();
2544 event.cancelBubble = true;
2545 event.returnValue = false;
2550 this.lastNormalKeyDownEvent = undefined;
2551 this.lastKeyPressedEvent = event;
2555 VT100.prototype.keyUp = function(event) {
2556 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2557 // (event.shiftKey || event.ctrlKey || event.altKey ||
2558 // event.metaKey ? ', ' +
2559 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2560 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2562 if (this.lastKeyPressedEvent) {
2563 // The compose key on Linux occasionally confuses the browser and keeps
2564 // inserting bogus characters into the input field, even if just a regular
2565 // key has been pressed. Detect this case and drop the bogus characters.
2567 event.srcElement).value = '';
2569 // This is usually were we notice that a key has been composed and
2570 // thus failed to generate normal events.
2571 this.checkComposedKeys(event);
2573 // Some browsers don't report keypress events if ctrl or alt is pressed
2574 // for non-alphanumerical keys. Patch things up for now, but in the
2575 // future we will catch these keys earlier (in the keydown handler).
2576 if (this.lastNormalKeyDownEvent) {
2577 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
2578 this.catchModifiersEarly = true;
2580 event.keyCode == 32 ||
2581 event.keyCode >= 48 && event.keyCode <= 57 ||
2582 event.keyCode >= 65 && event.keyCode <= 90;
2585 event.keyCode >= 96 && event.keyCode <= 105;
2588 event.keyCode == 59 || event.keyCode == 61 ||
2589 event.keyCode == 106 || event.keyCode == 107 ||
2590 event.keyCode >= 109 && event.keyCode <= 111 ||
2591 event.keyCode >= 186 && event.keyCode <= 192 ||
2592 event.keyCode >= 219 && event.keyCode <= 223 ||
2593 event.keyCode == 252;
2595 fake.ctrlKey = event.ctrlKey;
2596 fake.shiftKey = event.shiftKey;
2597 fake.altKey = event.altKey;
2598 fake.metaKey = event.metaKey;
2600 fake.charCode = event.keyCode;
2604 fake.keyCode = event.keyCode;
2605 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2606 fake = this.fixEvent(fake);
2609 this.lastNormalKeyDownEvent = undefined;
2610 this.handleKey(fake);
2616 event.cancelBubble = true;
2617 event.returnValue = false;
2622 this.lastKeyDownEvent = undefined;
2623 this.lastKeyPressedEvent = undefined;
2627 VT100.prototype.animateCursor = function(inactive) {
2628 if (!this.cursorInterval) {
2629 this.cursorInterval = setInterval(
2632 vt100.animateCursor();
2634 // Use this opportunity to check whether the user entered a composed
2635 // key, or whether somebody pasted text into the textfield.
2636 vt100.checkComposedKeys();
2640 if (inactive != undefined || this.cursor.className != 'inactive') {
2642 this.cursor.className = 'inactive';
2644 if (this.blinkingCursor) {
2645 this.cursor.className = this.cursor.className == 'bright'
2648 this.cursor.className = 'bright';
2654 VT100.prototype.blurCursor = function() {
2655 this.animateCursor(true);
2658 VT100.prototype.focusCursor = function() {
2659 this.animateCursor(false);
2662 VT100.prototype.flashScreen = function() {
2663 this.isInverted = !this.isInverted;
2664 this.refreshInvertedState();
2665 this.isInverted = !this.isInverted;
2666 setTimeout(function(vt100) {
2668 vt100.refreshInvertedState();
2673 VT100.prototype.beep = function() {
2674 if (this.visualBell) {
2681 this.beeper.src = 'beep.wav';
2688 VT100.prototype.bs = function() {
2689 if (this.cursorX > 0) {
2690 this.gotoXY(this.cursorX - 1, this.cursorY);
2691 this.needWrap = false;
2695 VT100.prototype.ht = function(count) {
2696 if (count == undefined) {
2699 var cx = this.cursorX;
2700 while (count-- > 0) {
2701 while (cx++ < this.terminalWidth) {
2702 var tabState = this.userTabStop[cx];
2703 if (tabState == false) {
2704 // Explicitly cleared tab stop
2706 } else if (tabState) {
2707 // Explicitly set tab stop
2710 // Default tab stop at each eighth column
2717 if (cx > this.terminalWidth - 1) {
2718 cx = this.terminalWidth - 1;
2720 if (cx != this.cursorX) {
2721 this.gotoXY(cx, this.cursorY);
2725 VT100.prototype.rt = function(count) {
2726 if (count == undefined) {
2729 var cx = this.cursorX;
2730 while (count-- > 0) {
2732 var tabState = this.userTabStop[cx];
2733 if (tabState == false) {
2734 // Explicitly cleared tab stop
2736 } else if (tabState) {
2737 // Explicitly set tab stop
2740 // Default tab stop at each eighth column
2750 if (cx != this.cursorX) {
2751 this.gotoXY(cx, this.cursorY);
2755 VT100.prototype.cr = function() {
2756 this.gotoXY(0, this.cursorY);
2757 this.needWrap = false;
2760 VT100.prototype.lf = function(count) {
2761 if (count == undefined) {
2764 if (count > this.terminalHeight) {
2765 count = this.terminalHeight;
2771 while (count-- > 0) {
2772 if (this.cursorY == this.bottom - 1) {
2773 this.scrollRegion(0, this.top + 1,
2774 this.terminalWidth, this.bottom - this.top - 1,
2775 0, -1, this.color, this.style);
2777 } else if (this.cursorY < this.terminalHeight - 1) {
2778 this.gotoXY(this.cursorX, this.cursorY + 1);
2783 VT100.prototype.ri = function(count) {
2784 if (count == undefined) {
2787 if (count > this.terminalHeight) {
2788 count = this.terminalHeight;
2794 while (count-- > 0) {
2795 if (this.cursorY == this.top) {
2796 this.scrollRegion(0, this.top,
2797 this.terminalWidth, this.bottom - this.top - 1,
2798 0, 1, this.color, this.style);
2799 } else if (this.cursorY > 0) {
2800 this.gotoXY(this.cursorX, this.cursorY - 1);
2803 this.needWrap = false;
2806 VT100.prototype.respondID = function() {
2807 this.respondString += '\u001B[?6c';
2810 VT100.prototype.respondSecondaryDA = function() {
2811 this.respondString += '\u001B[>0;0;0c';
2815 VT100.prototype.updateStyle = function() {
2817 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2818 this.style = 'text-decoration:underline;';
2820 var bg = (this.attr >> 4) & 0xF;
2821 var fg = this.attr & 0xF;
2822 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2827 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2828 fg = 8; // Dark grey
2829 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2832 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2835 // Make some readability enhancements. Most notably, disallow identical
2836 // background and foreground colors.
2838 if ((fg ^= 8) == 7) {
2842 // And disallow bright colors on a light-grey background.
2843 if (bg == 7 && fg >= 8) {
2844 if ((fg -= 8) == 7) {
2849 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2852 VT100.prototype.setAttrColors = function(attr) {
2853 if (attr != this.attr) {
2859 VT100.prototype.saveCursor = function() {
2860 this.savedX[this.currentScreen] = this.cursorX;
2861 this.savedY[this.currentScreen] = this.cursorY;
2862 this.savedAttr[this.currentScreen] = this.attr;
2863 this.savedUseGMap = this.useGMap;
2864 for (var i = 0; i < 4; i++) {
2865 this.savedGMap[i] = this.GMap[i];
2867 this.savedValid[this.currentScreen] = true;
2870 VT100.prototype.restoreCursor = function() {
2871 if (!this.savedValid[this.currentScreen]) {
2874 this.attr = this.savedAttr[this.currentScreen];
2876 this.useGMap = this.savedUseGMap;
2877 for (var i = 0; i < 4; i++) {
2878 this.GMap[i] = this.savedGMap[i];
2880 this.translate = this.GMap[this.useGMap];
2881 this.needWrap = false;
2882 this.gotoXY(this.savedX[this.currentScreen],
2883 this.savedY[this.currentScreen]);
2886 VT100.prototype.set80_132Mode = function(state) {
2887 var transform = undefined;
2888 var styles = [ 'transform',
2893 for (var i = 0; i < styles.length; ++i) {
2894 if (typeof this.console[0].style[styles[i]] != 'undefined') {
2895 transform = styles[i];
2901 if ((this.console[this.currentScreen].style[transform] != '') == state) {
2905 state ? transform == 'filter'
2906 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
2907 'M11=0.606060606060606060606,M12=0,M21=0,M22=1,' +
2908 "sizingMethod='auto expand')"
2909 : 'translateX(-50%) ' +
2910 'scaleX(0.606060606060606060606) ' +
2913 this.console[this.currentScreen].style[transform] = style;
2914 this.cursor.style[transform] = style;
2915 this.space.style[transform] = style;
2916 this.scale = state ? 1.65 : 1.0;
2917 if (transform == 'filter') {
2918 this.console[this.currentScreen].style.width = state ? '165%' : '';
2924 VT100.prototype.setMode = function(state) {
2925 for (var i = 0; i <= this.npar; i++) {
2926 if (this.isQuestionMark) {
2927 switch (this.par[i]) {
2928 case 1: this.cursorKeyMode = state; break;
2929 case 3: this.set80_132Mode(state); break;
2930 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2931 case 6: this.offsetMode = state; break;
2932 case 7: this.autoWrapMode = state; break;
2934 case 9: this.mouseReporting = state; break;
2935 case 25: this.cursorNeedsShowing = state;
2936 if (state) { this.showCursor(); }
2937 else { this.hideCursor(); } break;
2940 case 47: this.enableAlternateScreen(state); break;
2944 switch (this.par[i]) {
2945 case 3: this.dispCtrl = state; break;
2946 case 4: this.insertMode = state; break;
2947 case 20:this.crLfMode = state; break;
2954 VT100.prototype.statusReport = function() {
2955 // Ready and operational.
2956 this.respondString += '\u001B[0n';
2959 VT100.prototype.cursorReport = function() {
2960 this.respondString += '\u001B[' +
2961 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2963 (this.cursorX + 1) +
2967 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2968 // Changing of cursor color is not implemented.
2971 VT100.prototype.openPrinterWindow = function() {
2974 if (!this.printWin || this.printWin.closed) {
2975 this.printWin = window.open('', 'print-output',
2976 'width=800,height=600,directories=no,location=no,menubar=yes,' +
2977 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
2978 this.printWin.document.body.innerHTML =
2979 '<link rel="stylesheet" href="' +
2980 document.location.protocol + '//' + document.location.host +
2981 document.location.pathname.replace(/[^/]*$/, '') +
2982 'print-styles.css" type="text/css">\n' +
2983 '<div id="options"><input id="autoprint" type="checkbox"' +
2984 (this.autoprint ? ' checked' : '') + '>' +
2985 'Automatically, print page(s) when job is ready' +
2986 '</input></div>\n' +
2987 '<div id="spacer"><input type="checkbox"> </input></div>' +
2988 '<pre id="print"></pre>\n';
2989 var autoprint = this.printWin.document.getElementById('autoprint');
2990 this.addListener(autoprint, 'click',
2991 (function(vt100, autoprint) {
2993 vt100.autoprint = autoprint.checked;
2994 vt100.storeUserSettings();
2997 })(this, autoprint));
2998 this.printWin.document.title = 'ShellInABox Printer Output';
3001 // Maybe, a popup blocker prevented us from working. Better catch the
3002 // exception, so that we won't break the entire terminal session. The
3003 // user probably needs to disable the blocker first before retrying the
3007 rc &= this.printWin && !this.printWin.closed &&
3008 (this.printWin.innerWidth ||
3009 this.printWin.document.documentElement.clientWidth ||
3010 this.printWin.document.body.clientWidth) > 1;
3012 if (!rc && this.printing == 100) {
3013 // Different popup blockers work differently. We try to detect a couple
3014 // of common methods. And then we retry again a brief amount later, as
3015 // false positives are otherwise possible. If we are sure that there is
3016 // a popup blocker in effect, we alert the user to it. This is helpful
3017 // as some popup blockers have minimal or no UI, and the user might not
3018 // notice that they are missing the popup. In any case, we only show at
3019 // most one message per print job.
3020 this.printing = true;
3021 setTimeout((function(win) {
3023 if (!win || win.closed ||
3025 win.document.documentElement.clientWidth ||
3026 win.document.body.clientWidth) <= 1) {
3027 alert('Attempted to print, but a popup blocker ' +
3028 'prevented the printer window from opening');
3031 })(this.printWin), 2000);
3036 VT100.prototype.sendToPrinter = function(s) {
3037 this.openPrinterWindow();
3039 var doc = this.printWin.document;
3040 var print = doc.getElementById('print');
3041 if (print.lastChild && print.lastChild.nodeName == '#text') {
3042 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3044 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3047 // There probably was a more aggressive popup blocker that prevented us
3048 // from accessing the printer windows.
3052 VT100.prototype.sendControlToPrinter = function(ch) {
3053 // We get called whenever doControl() is active. But for the printer, we
3054 // only implement a basic line printer that doesn't understand most of
3055 // the escape sequences of the VT100 terminal. In fact, the only escape
3056 // sequence that we really need to recognize is '^[[5i' for turning the
3062 this.openPrinterWindow();
3063 var doc = this.printWin.document;
3064 var print = doc.getElementById('print');
3065 var chars = print.lastChild &&
3066 print.lastChild.nodeName == '#text' ?
3067 print.lastChild.textContent.length : 0;
3068 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3075 this.openPrinterWindow();
3076 var pageBreak = this.printWin.document.createElement('div');
3077 pageBreak.className = 'pagebreak';
3078 pageBreak.innerHTML = '<hr />';
3079 this.printWin.document.getElementById('print').appendChild(pageBreak);
3083 this.openPrinterWindow();
3084 var lineBreak = this.printWin.document.createElement('br');
3085 this.printWin.document.getElementById('print').appendChild(lineBreak);
3089 this.isEsc = 1 /* ESesc */;
3092 switch (this.isEsc) {
3094 this.isEsc = 0 /* ESnormal */;
3097 this.isEsc = 2 /* ESsquare */;
3103 case 2 /* ESsquare */:
3105 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3106 0, 0, 0, 0, 0, 0, 0, 0 ];
3107 this.isEsc = 3 /* ESgetpars */;
3108 this.isQuestionMark = ch == 0x3F /*?*/;
3109 if (this.isQuestionMark) {
3113 case 3 /* ESgetpars */:
3114 if (ch == 0x3B /*;*/) {
3117 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3118 var par = this.par[this.npar];
3119 if (par == undefined) {
3122 this.par[this.npar] = 10*par + (ch & 0xF);
3125 this.isEsc = 4 /* ESgotpars */;
3128 case 4 /* ESgotpars */:
3129 this.isEsc = 0 /* ESnormal */;
3130 if (this.isQuestionMark) {
3135 this.csii(this.par[0]);
3142 this.isEsc = 0 /* ESnormal */;
3148 // There probably was a more aggressive popup blocker that prevented us
3149 // from accessing the printer windows.
3153 VT100.prototype.csiAt = function(number) {
3158 if (number > this.terminalWidth - this.cursorX) {
3159 number = this.terminalWidth - this.cursorX;
3161 this.scrollRegion(this.cursorX, this.cursorY,
3162 this.terminalWidth - this.cursorX - number, 1,
3163 number, 0, this.color, this.style);
3164 this.needWrap = false;
3167 VT100.prototype.csii = function(number) {
3170 case 0: // Print Screen
3173 case 4: // Stop printing
3175 if (this.printing && this.printWin && !this.printWin.closed) {
3176 var print = this.printWin.document.getElementById('print');
3177 while (print.lastChild &&
3178 print.lastChild.tagName == 'DIV' &&
3179 print.lastChild.className == 'pagebreak') {
3180 // Remove trailing blank pages
3181 print.removeChild(print.lastChild);
3183 if (this.autoprint) {
3184 this.printWin.print();
3189 this.printing = false;
3191 case 5: // Start printing
3192 if (!this.printing && this.printWin && !this.printWin.closed) {
3193 this.printWin.document.getElementById('print').innerHTML = '';
3195 this.printing = 100;
3202 VT100.prototype.csiJ = function(number) {
3204 case 0: // Erase from cursor to end of display
3205 this.clearRegion(this.cursorX, this.cursorY,
3206 this.terminalWidth - this.cursorX, 1,
3207 this.color, this.style);
3208 if (this.cursorY < this.terminalHeight-2) {
3209 this.clearRegion(0, this.cursorY+1,
3210 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3211 this.color, this.style);
3214 case 1: // Erase from start to cursor
3215 if (this.cursorY > 0) {
3216 this.clearRegion(0, 0,
3217 this.terminalWidth, this.cursorY,
3218 this.color, this.style);
3220 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3221 this.color, this.style);
3223 case 2: // Erase whole display
3224 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3225 this.color, this.style);
3233 VT100.prototype.csiK = function(number) {
3235 case 0: // Erase from cursor to end of line
3236 this.clearRegion(this.cursorX, this.cursorY,
3237 this.terminalWidth - this.cursorX, 1,
3238 this.color, this.style);
3240 case 1: // Erase from start of line to cursor
3241 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3242 this.color, this.style);
3244 case 2: // Erase whole line
3245 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3246 this.color, this.style);
3254 VT100.prototype.csiL = function(number) {
3255 // Open line by inserting blank line(s)
3256 if (this.cursorY >= this.bottom) {
3262 if (number > this.bottom - this.cursorY) {
3263 number = this.bottom - this.cursorY;
3265 this.scrollRegion(0, this.cursorY,
3266 this.terminalWidth, this.bottom - this.cursorY - number,
3267 0, number, this.color, this.style);
3271 VT100.prototype.csiM = function(number) {
3272 // Delete line(s), scrolling up the bottom of the screen.
3273 if (this.cursorY >= this.bottom) {
3279 if (number > this.bottom - this.cursorY) {
3280 number = bottom - cursorY;
3282 this.scrollRegion(0, this.cursorY + number,
3283 this.terminalWidth, this.bottom - this.cursorY - number,
3284 0, -number, this.color, this.style);
3288 VT100.prototype.csim = function() {
3289 for (var i = 0; i <= this.npar; i++) {
3290 switch (this.par[i]) {
3291 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
3292 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
3293 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
3294 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
3295 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
3296 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
3298 this.translate = this.GMap[this.useGMap];
3299 this.dispCtrl = false;
3300 this.toggleMeta = false;
3303 this.translate = this.CodePage437Map;
3304 this.dispCtrl = true;
3305 this.toggleMeta = false;
3308 this.translate = this.CodePage437Map;
3309 this.dispCtrl = true;
3310 this.toggleMeta = true;
3313 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
3314 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
3315 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
3316 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
3317 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
3318 0x0200 /* ATTR_UNDERLINE */; break;
3319 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
3320 case 49: this.attr |= 0xF0; break;
3322 if (this.par[i] >= 30 && this.par[i] <= 37) {
3323 var fg = this.par[i] - 30;
3324 this.attr = (this.attr & ~0x0F) | fg;
3325 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3326 var bg = this.par[i] - 40;
3327 this.attr = (this.attr & ~0xF0) | (bg << 4);
3335 VT100.prototype.csiP = function(number) {
3336 // Delete character(s) following cursor
3340 if (number > this.terminalWidth - this.cursorX) {
3341 number = this.terminalWidth - this.cursorX;
3343 this.scrollRegion(this.cursorX + number, this.cursorY,
3344 this.terminalWidth - this.cursorX - number, 1,
3345 -number, 0, this.color, this.style);
3349 VT100.prototype.csiX = function(number) {
3350 // Clear characters following cursor
3354 if (number > this.terminalWidth - this.cursorX) {
3355 number = this.terminalWidth - this.cursorX;
3357 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3358 this.color, this.style);
3362 VT100.prototype.settermCommand = function() {
3363 // Setterm commands are not implemented
3366 VT100.prototype.doControl = function(ch) {
3367 if (this.printing) {
3368 this.sendControlToPrinter(ch);
3373 case 0x00: /* ignored */ break;
3374 case 0x08: this.bs(); break;
3375 case 0x09: this.ht(); break;
3379 case 0x84: this.lf(); if (!this.crLfMode) break;
3380 case 0x0D: this.cr(); break;
3381 case 0x85: this.cr(); this.lf(); break;
3382 case 0x0E: this.useGMap = 1;
3383 this.translate = this.GMap[1];
3384 this.dispCtrl = true; break;
3385 case 0x0F: this.useGMap = 0;
3386 this.translate = this.GMap[0];
3387 this.dispCtrl = false; break;
3389 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3390 case 0x1B: this.isEsc = 1 /* ESesc */; break;
3391 case 0x7F: /* ignored */ break;
3392 case 0x88: this.userTabStop[this.cursorX] = true; break;
3393 case 0x8D: this.ri(); break;
3394 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3395 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3396 case 0x9A: this.respondID(); break;
3397 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3398 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
3402 default: switch (this.isEsc) {
3404 this.isEsc = 0 /* ESnormal */;
3406 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3407 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3409 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3411 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3413 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3414 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3415 /*7*/ case 0x37: this.saveCursor(); break;
3416 /*8*/ case 0x38: this.restoreCursor(); break;
3417 /*>*/ case 0x3E: this.applKeyMode = false; break;
3418 /*=*/ case 0x3D: this.applKeyMode = true; break;
3419 /*D*/ case 0x44: this.lf(); break;
3420 /*E*/ case 0x45: this.cr(); this.lf(); break;
3421 /*M*/ case 0x4D: this.ri(); break;
3422 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3423 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3424 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3425 /*Z*/ case 0x5A: this.respondID(); break;
3426 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3427 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3428 /*c*/ case 0x63: this.reset(); break;
3429 /*g*/ case 0x67: this.flashScreen(); break;
3433 case 15 /* ESnonstd */:
3437 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3438 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3439 this.isEsc = 16 /* ESpalette */; break;
3440 /*R*/ case 0x52: // Palette support is not implemented
3441 this.isEsc = 0 /* ESnormal */; break;
3442 default: this.isEsc = 0 /* ESnormal */; break;
3445 case 16 /* ESpalette */:
3446 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3447 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3448 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3449 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3451 if (this.npar == 7) {
3452 // Palette support is not implemented
3453 this.isEsc = 0 /* ESnormal */;
3456 this.isEsc = 0 /* ESnormal */;
3459 case 2 /* ESsquare */:
3461 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3462 0, 0, 0, 0, 0, 0, 0, 0 ];
3463 this.isEsc = 3 /* ESgetpars */;
3464 /*[*/ if (ch == 0x5B) { // Function key
3465 this.isEsc = 6 /* ESfunckey */;
3468 /*?*/ this.isQuestionMark = ch == 0x3F;
3469 if (this.isQuestionMark) {
3474 case 5 /* ESdeviceattr */:
3475 case 3 /* ESgetpars */:
3476 /*;*/ if (ch == 0x3B) {
3479 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3480 var par = this.par[this.npar];
3481 if (par == undefined) {
3484 this.par[this.npar] = 10*par + (ch & 0xF);
3486 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3488 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3489 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3490 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3491 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3494 this.isEsc = 0 /* ESnormal */;
3497 this.isEsc = 4 /* ESgotpars */;
3500 case 4 /* ESgotpars */:
3501 this.isEsc = 0 /* ESnormal */;
3502 if (this.isQuestionMark) {
3504 /*h*/ case 0x68: this.setMode(true); break;
3505 /*l*/ case 0x6C: this.setMode(false); break;
3506 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3509 this.isQuestionMark = false;
3513 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3514 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3516 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3517 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3518 this.cursorY - (this.par[0] ? this.par[0] : 1));
3521 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3522 this.cursorY + (this.par[0] ? this.par[0] : 1));
3525 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3526 this.cursorY); break;
3527 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3528 this.cursorY); break;
3529 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3531 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3533 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3535 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3536 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3537 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3538 /*i*/ case 0x69: this.csii(this.par[0]); break;
3539 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3540 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3541 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3542 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3543 /*m*/ case 0x6D: this.csim(); break;
3544 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3545 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3546 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3547 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3548 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3549 /*g*/ case 0x67: if (this.par[0] == 0) {
3550 this.userTabStop[this.cursorX] = false;
3551 } else if (this.par[0] == 2 || this.par[0] == 3) {
3552 this.userTabStop = [ ];
3553 for (var i = 0; i < this.terminalWidth; i++) {
3554 this.userTabStop[i] = false;
3558 /*h*/ case 0x68: this.setMode(true); break;
3559 /*l*/ case 0x6C: this.setMode(false); break;
3560 /*n*/ case 0x6E: switch (this.par[0]) {
3561 case 5: this.statusReport(); break;
3562 case 6: this.cursorReport(); break;
3566 /*q*/ case 0x71: // LED control not implemented
3568 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3569 var b = this.par[1] ? this.par[1]
3570 : this.terminalHeight;
3571 if (t < b && b <= this.terminalHeight) {
3577 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3578 if (c > this.terminalWidth * this.terminalHeight) {
3579 c = this.terminalWidth * this.terminalHeight;
3582 lineBuf += this.lastCharacter;
3585 /*s*/ case 0x73: this.saveCursor(); break;
3586 /*u*/ case 0x75: this.restoreCursor(); break;
3587 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3588 /*]*/ case 0x5D: this.settermCommand(); break;
3592 case 12 /* ESbang */:
3596 this.isEsc = 0 /* ESnormal */;
3598 case 13 /* ESpercent */:
3599 this.isEsc = 0 /* ESnormal */;
3601 /*@*/ case 0x40: this.utfEnabled = false; break;
3603 /*8*/ case 0x38: this.utfEnabled = true; break;
3607 case 6 /* ESfunckey */:
3608 this.isEsc = 0 /* ESnormal */; break;
3609 case 7 /* EShash */:
3610 this.isEsc = 0 /* ESnormal */;
3611 /*8*/ if (ch == 0x38) {
3612 // Screen alignment test not implemented
3615 case 8 /* ESsetG0 */:
3616 case 9 /* ESsetG1 */:
3617 case 10 /* ESsetG2 */:
3618 case 11 /* ESsetG3 */:
3619 var g = this.isEsc - 8 /* ESsetG0 */;
3620 this.isEsc = 0 /* ESnormal */;
3622 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3624 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3625 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3626 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3629 if (this.useGMap == g) {
3630 this.translate = this.GMap[g];
3633 case 17 /* ESstatus */:
3635 if (this.statusString && this.statusString.charAt(0) == ';') {
3636 this.statusString = this.statusString.substr(1);
3639 window.status = this.statusString;
3642 this.isEsc = 0 /* ESnormal */;
3644 this.statusString += String.fromCharCode(ch);
3647 case 18 /* ESss2 */:
3648 case 19 /* ESss3 */:
3650 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3651 [this.toggleMeta ? (ch | 0x80) : ch];
3652 if ((ch & 0xFF00) == 0xF000) {
3654 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3655 this.isEsc = 0 /* ESnormal */; break;
3658 this.lastCharacter = String.fromCharCode(ch);
3659 lineBuf += this.lastCharacter;
3660 this.isEsc = 0 /* ESnormal */; break;
3662 this.isEsc = 0 /* ESnormal */; break;
3669 VT100.prototype.renderString = function(s, showCursor) {
3670 if (this.printing) {
3671 this.sendToPrinter(s);
3678 // We try to minimize the number of DOM operations by coalescing individual
3679 // characters into strings. This is a significant performance improvement.
3680 var incX = s.length;
3681 if (incX > this.terminalWidth - this.cursorX) {
3682 incX = this.terminalWidth - this.cursorX;
3686 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3689 // Minimize the number of calls to putString(), by avoiding a direct
3690 // call to this.showCursor()
3691 this.cursor.style.visibility = '';
3693 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3696 VT100.prototype.vt100 = function(s) {
3697 this.cursorNeedsShowing = this.hideCursor();
3698 this.respondString = '';
3700 for (var i = 0; i < s.length; i++) {
3701 var ch = s.charCodeAt(i);
3702 if (this.utfEnabled) {
3703 // Decode UTF8 encoded character
3705 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3706 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3707 if (--this.utfCount <= 0) {
3708 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3717 if ((ch & 0xE0) == 0xC0) {
3719 this.utfChar = ch & 0x1F;
3720 } else if ((ch & 0xF0) == 0xE0) {
3722 this.utfChar = ch & 0x0F;
3723 } else if ((ch & 0xF8) == 0xF0) {
3725 this.utfChar = ch & 0x07;
3726 } else if ((ch & 0xFC) == 0xF8) {
3728 this.utfChar = ch & 0x03;
3729 } else if ((ch & 0xFE) == 0xFC) {
3731 this.utfChar = ch & 0x01;
3741 var isNormalCharacter =
3742 (ch >= 32 && ch <= 127 || ch >= 160 ||
3743 this.utfEnabled && ch >= 128 ||
3744 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3745 (ch != 0x7F || this.dispCtrl);
3747 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3749 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3751 if ((ch & 0xFF00) == 0xF000) {
3753 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3756 if (!this.printing) {
3757 if (this.needWrap || this.insertMode) {
3759 this.renderString(lineBuf);
3763 if (this.needWrap) {
3764 this.cr(); this.lf();
3766 if (this.insertMode) {
3767 this.scrollRegion(this.cursorX, this.cursorY,
3768 this.terminalWidth - this.cursorX - 1, 1,
3769 1, 0, this.color, this.style);
3772 this.lastCharacter = String.fromCharCode(ch);
3773 lineBuf += this.lastCharacter;
3774 if (!this.printing &&
3775 this.cursorX + lineBuf.length >= this.terminalWidth) {
3776 this.needWrap = this.autoWrapMode;
3780 this.renderString(lineBuf);
3783 var expand = this.doControl(ch);
3784 if (expand.length) {
3785 var r = this.respondString;
3786 this.respondString= r + this.vt100(expand);
3791 this.renderString(lineBuf, this.cursorNeedsShowing);
3792 } else if (this.cursorNeedsShowing) {
3795 return this.respondString;
3798 VT100.prototype.Latin1Map = [
3799 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3800 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3801 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3802 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3803 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3804 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3805 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3806 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3807 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3808 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3809 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3810 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3811 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3812 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3813 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3814 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3815 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3816 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3817 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3818 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3819 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3820 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3821 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3822 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3823 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3824 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3825 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3826 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3827 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3828 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3829 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3830 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3833 VT100.prototype.VT100GraphicsMap = [
3834 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3835 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3836 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3837 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3838 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3839 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3840 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3841 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3842 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3843 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3844 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3845 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3846 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3847 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3848 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3849 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3850 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3851 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3852 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3853 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3854 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3855 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3856 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3857 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3858 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3859 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3860 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3861 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3862 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3863 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3864 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3865 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3868 VT100.prototype.CodePage437Map = [
3869 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3870 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3871 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3872 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3873 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3874 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3875 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3876 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3877 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3878 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3879 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3880 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3881 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3882 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3883 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3884 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3885 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3886 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3887 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3888 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3889 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3890 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3891 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3892 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3893 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3894 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3895 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3896 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3897 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3898 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3899 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3900 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3903 VT100.prototype.DirectToFontMap = [
3904 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3905 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3906 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3907 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3908 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3909 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3910 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3911 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3912 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3913 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3914 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3915 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3916 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3917 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3918 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3919 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3920 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3921 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3922 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3923 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3924 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3925 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3926 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3927 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3928 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3929 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3930 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3931 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3932 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3933 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3934 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3935 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3938 VT100.prototype.ctrlAction = [
3939 true, false, false, false, false, false, false, true,
3940 true, true, true, true, true, true, true, true,
3941 false, false, false, false, false, false, false, false,
3942 true, false, true, true, false, false, false, false
3945 VT100.prototype.ctrlAlways = [
3946 true, false, false, false, false, false, false, false,
3947 true, false, true, false, true, true, true, true,
3948 false, false, false, false, false, false, false, false,
3949 false, false, false, true, false, false, false, false