1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2009 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);
241 this.isInverted = false;
242 this.refreshInvertedState();
243 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
244 this.color, this.style);
247 VT100.prototype.addListener = function(elem, event, listener) {
248 if (elem.addEventListener) {
249 elem.addEventListener(event, listener, false);
251 elem.attachEvent('on' + event, listener);
255 VT100.prototype.getUserSettings = function() {
256 // Compute hash signature to identify the entries in the userCSS menu.
257 // If the menu is unchanged from last time, default values can be
258 // looked up in a cookie associated with this page.
260 this.utfPreferred = true;
261 this.visualBell = typeof suppressAllAudio != 'undefined' &&
263 this.autoprint = true;
264 if (this.visualBell) {
265 this.signature = Math.floor(16807*this.signature + 1) %
268 if (typeof userCSSList != 'undefined') {
269 for (var i = 0; i < userCSSList.length; ++i) {
270 var label = userCSSList[i][0];
271 for (var j = 0; j < label.length; ++j) {
272 this.signature = Math.floor(16807*this.signature+
273 label.charCodeAt(j)) %
276 if (userCSSList[i][1]) {
277 this.signature = Math.floor(16807*this.signature + 1) %
283 var key = 'shellInABox=' + this.signature + ':';
284 var settings = document.cookie.indexOf(key);
286 settings = document.cookie.substr(settings + key.length).
287 replace(/([0-1]*).*/, "$1");
288 if (settings.length == 3 + (typeof userCSSList == 'undefined' ?
289 0 : userCSSList.length)) {
290 this.utfPreferred = settings.charAt(0) != '0';
291 this.visualBell = settings.charAt(1) != '0';
292 this.autoprint = settings.charAt(2) != '0';
293 if (typeof userCSSList != 'undefined') {
294 for (var i = 0; i < userCSSList.length; ++i) {
295 userCSSList[i][2] = settings.charAt(i + 3) != '0';
300 this.utfEnabled = this.utfPreferred;
303 VT100.prototype.storeUserSettings = function() {
304 var settings = 'shellInABox=' + this.signature + ':' +
305 (this.utfEnabled ? '1' : '0') +
306 (this.visualBell ? '1' : '0') +
307 (this.autoprint ? '1' : '0');
308 if (typeof userCSSList != 'undefined') {
309 for (var i = 0; i < userCSSList.length; ++i) {
310 settings += userCSSList[i][2] ? '1' : '0';
314 d.setDate(d.getDate() + 3653);
315 document.cookie = settings + ';expires=' + d.toGMTString();
318 VT100.prototype.initializeUserCSSStyles = function() {
319 this.usercssActions = [];
320 if (typeof userCSSList != 'undefined') {
323 var wasSingleSel = 1;
324 var beginOfGroup = 0;
325 for (var i = 0; i <= userCSSList.length; ++i) {
326 if (i < userCSSList.length) {
327 var label = userCSSList[i][0];
328 var newGroup = userCSSList[i][1];
329 var enabled = userCSSList[i][2];
331 // Add user style sheet to document
332 var style = document.createElement('link');
333 var id = document.createAttribute('id');
334 id.nodeValue = 'usercss-' + i;
335 style.setAttributeNode(id);
336 var rel = document.createAttribute('rel');
337 rel.nodeValue = 'stylesheet';
338 style.setAttributeNode(rel);
339 var href = document.createAttribute('href');
340 href.nodeValue = 'usercss-' + i + '.css';
341 style.setAttributeNode(href);
342 var type = document.createAttribute('type');
343 type.nodeValue = 'text/css';
344 style.setAttributeNode(type);
345 document.getElementsByTagName('head')[0].appendChild(style);
346 style.disabled = !enabled;
350 if (newGroup || i == userCSSList.length) {
351 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
352 // The last group had multiple entries that are mutually exclusive;
353 // or the previous to last group did. In either case, we need to
354 // append a "<hr />" before we can add the last group to the menu.
357 wasSingleSel = i - beginOfGroup < 1;
361 for (var j = beginOfGroup; j < i; ++j) {
362 this.usercssActions[this.usercssActions.length] =
363 function(vt100, current, begin, count) {
365 // Deselect all other entries in the group, then either select
366 // (for multiple entries in group) or toggle (for on/off entry)
367 // the current entry.
369 var entry = vt100.getChildById(vt100.menu,
373 for (var c = count; c > 0; ++j) {
374 if (entry.tagName == 'LI') {
377 var label = vt100.usercss.childNodes[j];
379 // Restore label to just the text content
380 if (typeof label.textContent == 'undefined') {
381 var s = label.innerText;
382 label.innerHTML = '';
383 label.appendChild(document.createTextNode(s));
385 label.textContent= label.textContent;
388 // User style sheets are number sequentially
389 var sheet = document.getElementById(
393 sheet.disabled = !sheet.disabled;
395 sheet.disabled = false;
397 if (!sheet.disabled) {
398 label.innerHTML= '<img src="enabled.gif" />' +
402 sheet.disabled = true;
404 userCSSList[i][2] = !sheet.disabled;
407 entry = entry.nextSibling;
410 }(this, j, beginOfGroup, i - beginOfGroup);
413 if (i == userCSSList.length) {
419 // Collect all entries in a group, before attaching them to the menu.
420 // This is necessary as we don't know whether this is a group of
421 // mutually exclusive options (which should be separated by "<hr />" on
422 // both ends), or whether this is a on/off toggle, which can be grouped
423 // together with other on/off options.
425 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
429 this.usercss.innerHTML = menu;
433 VT100.prototype.initializeElements = function(container) {
434 // If the necessary objects have not already been defined in the HTML
435 // page, create them now.
437 this.container = container;
438 } else if (!(this.container = document.getElementById('vt100'))) {
439 this.container = document.createElement('div');
440 this.container.id = 'vt100';
441 document.body.appendChild(this.container);
444 if (!this.getChildById(this.container, 'reconnect') ||
445 !this.getChildById(this.container, 'menu') ||
446 !this.getChildById(this.container, 'scrollable') ||
447 !this.getChildById(this.container, 'console') ||
448 !this.getChildById(this.container, 'alt_console') ||
449 !this.getChildById(this.container, 'ieprobe') ||
450 !this.getChildById(this.container, 'padding') ||
451 !this.getChildById(this.container, 'cursor') ||
452 !this.getChildById(this.container, 'lineheight') ||
453 !this.getChildById(this.container, 'usercss') ||
454 !this.getChildById(this.container, 'space') ||
455 !this.getChildById(this.container, 'input') ||
456 !this.getChildById(this.container, 'cliphelper')) {
457 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
458 // we might get a pointless warning that a suitable plugin is not yet
459 // installed. If in doubt, we'd rather just stay silent.
462 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
464 embed = typeof suppressAllAudio != 'undefined' &&
465 suppressAllAudio ? "" :
466 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
469 'autostart="false" ' +
471 'enablejavascript="true" ' +
472 'type="audio/x-wav" ' +
475 'style="position:absolute;left:-1000px;top:-1000px" />';
480 this.container.innerHTML =
481 '<div id="reconnect" style="visibility: hidden">' +
482 '<input type="button" value="Connect" ' +
483 'onsubmit="return false" />' +
485 '<div id="cursize" style="visibility: hidden">' +
487 '<div id="menu"></div>' +
488 '<div id="scrollable">' +
489 '<pre id="lineheight"> </pre>' +
490 '<pre id="console">' +
492 '<div id="ieprobe"><span> </span></div>' +
494 '<pre id="alt_console" style="display: none"></pre>' +
495 '<div id="padding"></div>' +
496 '<pre id="cursor"> </pre>' +
498 '<div class="hidden">' +
499 '<div id="usercss"></div>' +
500 '<pre><div><span id="space"></span></div></pre>' +
501 '<input type="textfield" id="input" />' +
502 '<input type="textfield" id="cliphelper" />' +
503 (typeof suppressAllAudio != 'undefined' &&
504 suppressAllAudio ? "" :
505 embed + '<bgsound id="beep_bgsound" loop=1 />') +
509 // Find the object used for playing the "beep" sound, if any.
510 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
511 this.beeper = undefined;
513 this.beeper = this.getChildById(this.container,
515 if (!this.beeper || !this.beeper.Play) {
516 this.beeper = this.getChildById(this.container,
518 if (!this.beeper || typeof this.beeper.src == 'undefined') {
519 this.beeper = undefined;
524 // Initialize the variables for finding the text console and the
526 this.reconnectBtn = this.getChildById(this.container,'reconnect');
527 this.curSizeBox = this.getChildById(this.container, 'cursize');
528 this.menu = this.getChildById(this.container, 'menu');
529 this.scrollable = this.getChildById(this.container,
531 this.lineheight = this.getChildById(this.container,
534 [ this.getChildById(this.container, 'console'),
535 this.getChildById(this.container, 'alt_console') ];
536 var ieProbe = this.getChildById(this.container, 'ieprobe');
537 this.padding = this.getChildById(this.container, 'padding');
538 this.cursor = this.getChildById(this.container, 'cursor');
539 this.usercss = this.getChildById(this.container, 'usercss');
540 this.space = this.getChildById(this.container, 'space');
541 this.input = this.getChildById(this.container, 'input');
542 this.cliphelper = this.getChildById(this.container,
545 // Add any user selectable style sheets to the menu
546 this.initializeUserCSSStyles();
548 // Remember the dimensions of a standard character glyph. We would
549 // expect that we could just check cursor.clientWidth/Height at any time,
550 // but it turns out that browsers sometimes invalidate these values
551 // (e.g. while displaying a print preview screen).
552 this.cursorWidth = this.cursor.clientWidth;
553 this.cursorHeight = this.lineheight.clientHeight;
555 // IE has a slightly different boxing model, that we need to compensate for
556 this.isIE = ieProbe.offsetTop > 1;
558 this.console.innerHTML = '';
560 // Determine if the terminal window is positioned at the beginning of the
561 // page, or if it is embedded somewhere else in the page. For full-screen
562 // terminals, automatically resize whenever the browser window changes.
563 var marginTop = parseInt(this.getCurrentComputedStyle(
564 document.body, 'marginTop'));
565 var marginLeft = parseInt(this.getCurrentComputedStyle(
566 document.body, 'marginLeft'));
567 var marginRight = parseInt(this.getCurrentComputedStyle(
568 document.body, 'marginRight'));
569 var x = this.container.offsetLeft;
570 var y = this.container.offsetTop;
571 for (var parent = this.container; parent = parent.offsetParent; ) {
572 x += parent.offsetLeft;
573 y += parent.offsetTop;
575 this.isEmbedded = marginTop != y ||
577 (window.innerWidth ||
578 document.documentElement.clientWidth ||
579 document.body.clientWidth) -
580 marginRight != x + this.container.offsetWidth;
581 if (!this.isEmbedded) {
582 // Some browsers generate resize events when the terminal is first
583 // shown. Disable showing the size indicator until a little bit after
584 // the terminal has been rendered the first time.
585 this.indicateSize = false;
586 setTimeout(function(vt100) {
588 vt100.indicateSize = true;
591 this.addListener(window, 'resize',
594 vt100.hideContextMenu();
596 vt100.showCurrentSize();
600 // Hide extra scrollbars attached to window
601 document.body.style.margin = '0px';
602 try { document.body.style.overflow ='hidden'; } catch (e) { }
603 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
607 this.hideContextMenu();
609 // Add listener to reconnect button
610 this.addListener(this.reconnectBtn.firstChild, 'click',
613 var rc = vt100.reconnect();
619 // Add input listeners
620 this.addListener(this.input, 'blur',
622 return function() { vt100.blurCursor(); } }(this));
623 this.addListener(this.input, 'focus',
625 return function() { vt100.focusCursor(); } }(this));
626 this.addListener(this.input, 'keydown',
629 if (!e) e = window.event;
630 return vt100.keyDown(e); } }(this));
631 this.addListener(this.input, 'keypress',
634 if (!e) e = window.event;
635 return vt100.keyPressed(e); } }(this));
636 this.addListener(this.input, 'keyup',
639 if (!e) e = window.event;
640 return vt100.keyUp(e); } }(this));
642 // Attach listeners that move the focus to the <input> field. This way we
643 // can make sure that we can receive keyboard input.
644 var mouseEvent = function(vt100, type) {
646 if (!e) e = window.event;
647 return vt100.mouseEvent(e, type);
650 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
651 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
652 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
654 // Initialize the blank terminal window.
655 this.currentScreen = 0;
658 this.numScrollbackLines = 0;
660 this.bottom = 0x7FFFFFFF;
666 VT100.prototype.getChildById = function(parent, id) {
667 var nodeList = parent.all || parent.getElementsByTagName('*');
668 if (typeof nodeList.namedItem == 'undefined') {
669 for (var i = 0; i < nodeList.length; i++) {
670 if (nodeList[i].id == id) {
676 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
677 return elem ? elem[0] || elem : null;
681 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
682 if (typeof elem.currentStyle != 'undefined') {
683 return elem.currentStyle[style];
685 return document.defaultView.getComputedStyle(elem, null)[style];
689 VT100.prototype.reconnect = function() {
693 VT100.prototype.showReconnect = function(state) {
695 this.reconnectBtn.style.visibility = '';
697 this.reconnectBtn.style.visibility = 'hidden';
701 VT100.prototype.repairElements = function(console) {
702 for (var line = console.firstChild; line; line = line.nextSibling) {
703 if (!line.clientHeight) {
704 var newLine = document.createElement(line.tagName);
705 newLine.style.cssText = line.style.cssText;
706 newLine.className = line.className;
707 if (line.tagName == 'DIV') {
708 for (var span = line.firstChild; span; span = span.nextSibling) {
709 var newSpan = document.createElement(span.tagName);
710 newSpan.style.cssText = span.style.cssText;
711 newSpan.style.className = span.style.className;
712 this.setTextContent(newSpan, this.getTextContent(span));
713 newLine.appendChild(newSpan);
716 this.setTextContent(newLine, this.getTextContent(line));
718 line.parentNode.replaceChild(newLine, line);
724 VT100.prototype.resized = function(w, h) {
727 VT100.prototype.resizer = function() {
728 // The cursor can get corrupted if the print-preview is displayed in Firefox.
729 // Recreating it, will repair it.
730 var newCursor = document.createElement('pre');
731 this.setTextContent(newCursor, ' ');
732 newCursor.id = 'cursor';
733 newCursor.style.cssText = this.cursor.style.cssText;
734 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
735 if (!newCursor.clientHeight) {
736 // Things are broken right now. This is probably because we are
737 // displaying the print-preview. Just don't change any of our settings
738 // until the print dialog is closed again.
739 newCursor.parentNode.removeChild(newCursor);
742 // Swap the old broken cursor for the newly created one.
743 this.cursor.parentNode.removeChild(this.cursor);
744 this.cursor = newCursor;
747 // Really horrible things happen if the contents of the terminal changes
748 // while the print-preview is showing. We get HTML elements that show up
749 // in the DOM, but that do not take up any space. Find these elements and
751 this.repairElements(this.console[0]);
752 this.repairElements(this.console[1]);
754 // Lock the cursor size to the size of a normal character. This helps with
755 // characters that are taller/shorter than normal. Unfortunately, we will
756 // still get confused if somebody enters a character that is wider/narrower
757 // than normal. This can happen if the browser tries to substitute a
758 // characters from a different font.
759 this.cursor.style.width = this.cursorWidth + 'px';
760 this.cursor.style.height = this.cursorHeight + 'px';
762 // Adjust height for one pixel padding of the #vt100 element.
763 // The latter is necessary to properly display the inactive cursor.
764 var console = this.console[this.currentScreen];
765 var height = (this.isEmbedded ? this.container.clientHeight
766 : (window.innerHeight ||
767 document.documentElement.clientHeight ||
768 document.body.clientHeight))-1;
769 var partial = height % this.cursorHeight;
770 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
771 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
772 var oldTerminalHeight = this.terminalHeight;
776 // Clip the cursor to the visible screen.
777 var cx = this.cursorX;
778 var cy = this.cursorY + this.numScrollbackLines;
780 // The alternate screen never keeps a scroll back buffer.
781 this.updateNumScrollbackLines();
782 while (this.currentScreen && this.numScrollbackLines > 0) {
783 console.removeChild(console.firstChild);
784 this.numScrollbackLines--;
786 cy -= this.numScrollbackLines;
789 } else if (cx > this.terminalWidth) {
790 cx = this.terminalWidth - 1;
797 } else if (cy > this.terminalHeight) {
798 cy = this.terminalHeight - 1;
804 // Clip the scroll region to the visible screen.
805 if (this.bottom > this.terminalHeight ||
806 this.bottom == oldTerminalHeight) {
807 this.bottom = this.terminalHeight;
809 if (this.top >= this.bottom) {
810 this.top = this.bottom-1;
816 // Truncate lines, if necessary. Explicitly reposition cursor (this is
817 // particularly important after changing the screen number), and reset
818 // the scroll region to the default.
819 this.truncateLines(this.terminalWidth);
820 this.putString(cx, cy, '', undefined);
821 this.scrollable.scrollTop = this.numScrollbackLines *
822 this.cursorHeight + 1;
824 // Update classNames for lines in the scrollback buffer
825 var line = console.firstChild;
826 for (var i = 0; i < this.numScrollbackLines; i++) {
827 line.className = 'scrollback';
828 line = line.nextSibling;
832 line = line.nextSibling;
835 // Reposition the reconnect button
836 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
837 this.reconnectBtn.clientWidth)/2 + 'px';
838 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
839 this.reconnectBtn.clientHeight)/2 + 'px';
841 // Send notification that the window size has been changed
842 this.resized(this.terminalWidth, this.terminalHeight);
845 VT100.prototype.showCurrentSize = function() {
846 if (!this.indicateSize) {
849 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
851 this.curSizeBox.style.left =
852 (this.terminalWidth*this.cursorWidth -
853 this.curSizeBox.clientWidth)/2 + 'px';
854 this.curSizeBox.style.top =
855 (this.terminalHeight*this.cursorHeight -
856 this.curSizeBox.clientHeight)/2 + 'px';
857 this.curSizeBox.style.visibility = '';
858 if (this.curSizeTimeout) {
859 clearTimeout(this.curSizeTimeout);
862 // Only show the terminal size for a short amount of time after resizing.
863 // Then hide this information, again. Some browsers generate resize events
864 // throughout the entire resize operation. This is nice, and we will show
865 // the terminal size while the user is dragging the window borders.
866 // Other browsers only generate a single event when the user releases the
867 // mouse. In those cases, we can only show the terminal size once at the
868 // end of the resize operation.
869 this.curSizeTimeout = setTimeout(function(vt100) {
871 vt100.curSizeTimeout = null;
872 vt100.curSizeBox.style.visibility = 'hidden';
877 VT100.prototype.selection = function() {
879 return '' + (window.getSelection && window.getSelection() ||
880 document.selection && document.selection.type == 'Text' &&
881 document.selection.createRange().text || '');
887 VT100.prototype.cancelEvent = function(event) {
889 // For non-IE browsers
890 event.stopPropagation();
891 event.preventDefault();
896 event.cancelBubble = true;
897 event.returnValue = false;
905 VT100.prototype.mouseEvent = function(event, type) {
906 // If any text is currently selected, do not move the focus as that would
907 // invalidate the selection.
908 var selection = this.selection();
909 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
913 // Compute mouse position in characters.
914 var offsetX = this.container.offsetLeft;
915 var offsetY = this.container.offsetTop;
916 for (var e = this.container; e = e.offsetParent; ) {
917 offsetX += e.offsetLeft;
918 offsetY += e.offsetTop;
920 var x = (event.clientX - offsetX) / this.cursorWidth;
921 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
922 this.cursorHeight - this.numScrollbackLines;
924 if (x >= this.terminalWidth) {
925 x = this.terminalWidth - 1;
932 if (y >= this.terminalHeight) {
933 y = this.terminalHeight - 1;
941 // Compute button number and modifier keys.
942 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
943 typeof event.pageX != 'undefined' ? event.button :
944 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
945 if (button != undefined) {
946 if (event.shiftKey) {
949 if (event.altKey || event.metaKey) {
957 // Report mouse events if they happen inside of the current screen and
958 // with the SHIFT key unpressed. Both of these restrictions do not apply
959 // for button releases, as we always want to report those.
960 if (this.mouseReporting && !selection.length &&
961 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
962 if (inside || type != 0 /* MOUSE_DOWN */) {
963 if (button != undefined) {
964 var report = '\u001B[M' + String.fromCharCode(button + 32) +
965 String.fromCharCode(x + 33) +
966 String.fromCharCode(y + 33);
967 if (type != 2 /* MOUSE_CLICK */) {
968 this.keysPressed(report);
971 // If we reported the event, stop propagating it (not sure, if this
972 // actually works on most browsers; blocking the global "oncontextmenu"
973 // even is still necessary).
974 return this.cancelEvent(event);
979 // Bring up context menu.
980 if (button == 2 && !event.shiftKey) {
981 if (type == 0 /* MOUSE_DOWN */) {
982 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
984 return this.cancelEvent(event);
987 if (this.mouseReporting) {
989 event.shiftKey = false;
997 VT100.prototype.replaceChar = function(s, ch, repl) {
999 i = s.indexOf(ch, i + 1);
1003 s = s.substr(0, i) + repl + s.substr(i + 1);
1008 VT100.prototype.htmlEscape = function(s) {
1009 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1010 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1013 VT100.prototype.getTextContent = function(elem) {
1014 return elem.textContent ||
1015 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1018 VT100.prototype.setTextContent = function(elem, s) {
1019 // Check if we find any URLs in the text. If so, automatically convert them
1021 if (this.urlRE && this.urlRE.test(s)) {
1025 if (RegExp.leftContext != null) {
1026 inner += this.htmlEscape(RegExp.leftContext);
1027 consumed += RegExp.leftContext.length;
1029 var url = this.htmlEscape(RegExp.lastMatch);
1032 // If no protocol was specified, try to guess a reasonable one.
1033 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1034 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1035 var slash = url.indexOf('/');
1036 var at = url.indexOf('@');
1037 var question = url.indexOf('?');
1039 (at < question || question < 0) &&
1040 (slash < 0 || (question > 0 && slash > question))) {
1041 fullUrl = 'mailto:' + url;
1043 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1048 inner += '<a target="vt100Link" href="' + fullUrl +
1049 '">' + url + '</a>';
1050 consumed += RegExp.lastMatch.length;
1051 s = s.substr(consumed);
1052 if (!this.urlRE.test(s)) {
1053 if (RegExp.rightContext != null) {
1054 inner += this.htmlEscape(RegExp.rightContext);
1059 elem.innerHTML = inner;
1063 // Updating the content of an element is an expensive operation. It actually
1064 // pays off to first check whether the element is still unchanged.
1065 if (typeof elem.textContent == 'undefined') {
1066 if (elem.innerText != s) {
1070 // Very old versions of IE do not allow setting innerText. Instead,
1071 // remove all children, by setting innerHTML and then set the text
1072 // using DOM methods.
1073 elem.innerHTML = '';
1074 elem.appendChild(document.createTextNode(
1075 this.replaceChar(s, ' ', '\u00A0')));
1079 if (elem.textContent != s) {
1080 elem.textContent = s;
1085 VT100.prototype.insertBlankLine = function(y, color, style) {
1086 // Insert a blank line a position y. This method ignores the scrollback
1087 // buffer. The caller has to add the length of the scrollback buffer to
1088 // the position, if necessary.
1089 // If the position is larger than the number of current lines, this
1090 // method just adds a new line right after the last existing one. It does
1091 // not add any missing lines in between. It is the caller's responsibility
1094 color = 'ansi0 bgAnsi15';
1100 if (color != 'ansi0 bgAnsi15' && !style) {
1101 line = document.createElement('pre');
1102 this.setTextContent(line, '\n');
1104 line = document.createElement('div');
1105 var span = document.createElement('span');
1106 span.style.cssText = style;
1107 span.style.className = color;
1108 this.setTextContent(span, this.spaces(this.terminalWidth));
1109 line.appendChild(span);
1111 line.style.height = this.cursorHeight + 'px';
1112 var console = this.console[this.currentScreen];
1113 if (console.childNodes.length > y) {
1114 console.insertBefore(line, console.childNodes[y]);
1116 console.appendChild(line);
1120 VT100.prototype.updateWidth = function() {
1121 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1123 return this.terminalWidth;
1126 VT100.prototype.updateHeight = function() {
1127 // We want to be able to display either a terminal window that fills the
1128 // entire browser window, or a terminal window that is contained in a
1129 // <div> which is embededded somewhere in the web page.
1130 if (this.isEmbedded) {
1131 // Embedded terminal. Use size of the containing <div> (id="vt100").
1132 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1135 // Use the full browser window.
1136 this.terminalHeight = Math.floor(((window.innerHeight ||
1137 document.documentElement.clientHeight ||
1138 document.body.clientHeight)-1)/
1141 return this.terminalHeight;
1144 VT100.prototype.updateNumScrollbackLines = function() {
1145 var scrollback = Math.floor(
1146 this.console[this.currentScreen].offsetHeight /
1147 this.cursorHeight) -
1148 this.terminalHeight;
1149 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1150 return this.numScrollbackLines;
1153 VT100.prototype.truncateLines = function(width) {
1157 for (var line = this.console[this.currentScreen].firstChild; line;
1158 line = line.nextSibling) {
1159 if (line.tagName == 'DIV') {
1162 // Traverse current line and truncate it once we saw "width" characters
1163 for (var span = line.firstChild; span;
1164 span = span.nextSibling) {
1165 var s = this.getTextContent(span);
1167 if (x + l > width) {
1168 this.setTextContent(span, s.substr(0, width - x));
1169 while (span.nextSibling) {
1170 line.removeChild(line.lastChild);
1176 // Prune white space from the end of the current line
1177 var span = line.lastChild;
1179 span.className == 'ansi0 bgAnsi15' &&
1180 !span.style.cssText.length) {
1181 // Scan backwards looking for first non-space character
1182 var s = this.getTextContent(span);
1183 for (var i = s.length; i--; ) {
1184 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1185 if (i+1 != s.length) {
1186 this.setTextContent(s.substr(0, i+1));
1194 span = span.previousSibling;
1196 // Remove blank <span>'s from end of line
1197 line.removeChild(sibling);
1199 // Remove entire line (i.e. <div>), if empty
1200 var blank = document.createElement('pre');
1201 blank.style.height = this.cursorHeight + 'px';
1202 this.setTextContent(blank, '\n');
1203 line.parentNode.replaceChild(blank, line);
1211 VT100.prototype.putString = function(x, y, text, color, style) {
1213 color = 'ansi0 bgAnsi15';
1218 var yIdx = y + this.numScrollbackLines;
1224 var console = this.console[this.currentScreen];
1225 if (!text.length && (yIdx >= console.childNodes.length ||
1226 console.childNodes[yIdx].tagName != 'DIV')) {
1227 // Positioning cursor to a blank location
1230 // Create missing blank lines at end of page
1231 while (console.childNodes.length <= yIdx) {
1232 // In order to simplify lookups, we want to make sure that each line
1233 // is represented by exactly one element (and possibly a whole bunch of
1235 // For non-blank lines, we can create a <div> containing one or more
1236 // <span>s. For blank lines, this fails as browsers tend to optimize them
1237 // away. But fortunately, a <pre> tag containing a newline character
1238 // appears to work for all browsers (a would also work, but then
1239 // copying from the browser window would insert superfluous spaces into
1241 this.insertBlankLine(yIdx);
1243 line = console.childNodes[yIdx];
1245 // If necessary, promote blank '\n' line to a <div> tag
1246 if (line.tagName != 'DIV') {
1247 var div = document.createElement('div');
1248 div.style.height = this.cursorHeight + 'px';
1249 div.innerHTML = '<span></span>';
1250 console.replaceChild(div, line);
1254 // Scan through list of <span>'s until we find the one where our text
1256 span = line.firstChild;
1258 while (span.nextSibling && xPos < x) {
1259 len = this.getTextContent(span).length;
1260 if (xPos + len > x) {
1264 span = span.nextSibling;
1268 // If current <span> is not long enough, pad with spaces or add new
1270 s = this.getTextContent(span);
1271 var oldColor = span.className;
1272 var oldStyle = span.style.cssText;
1273 if (xPos + s.length < x) {
1274 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1275 span = document.createElement('span');
1276 line.appendChild(span);
1277 span.className = 'ansi0 bgAnsi15';
1278 span.style.cssText = '';
1279 oldColor = 'ansi0 bgAnsi15';
1286 } while (xPos + s.length < x);
1289 // If styles do not match, create a new <span>
1290 var del = text.length - s.length + x - xPos;
1291 if (oldColor != color ||
1292 (oldStyle != style && (oldStyle || style))) {
1294 // Replacing text at beginning of existing <span>
1295 if (text.length >= s.length) {
1296 // New text is equal or longer than existing text
1299 // Insert new <span> before the current one, then remove leading
1300 // part of existing <span>, adjust style of new <span>, and finally
1302 sibling = document.createElement('span');
1303 line.insertBefore(sibling, span);
1304 this.setTextContent(span, s.substr(text.length));
1309 // Replacing text some way into the existing <span>
1310 var remainder = s.substr(x + text.length - xPos);
1311 this.setTextContent(span, s.substr(0, x - xPos));
1313 sibling = document.createElement('span');
1314 if (span.nextSibling) {
1315 line.insertBefore(sibling, span.nextSibling);
1317 if (remainder.length) {
1318 sibling = document.createElement('span');
1319 sibling.className = oldColor;
1320 sibling.style.cssText = oldStyle;
1321 this.setTextContent(sibling, remainder);
1322 line.insertBefore(sibling, span.nextSibling);
1325 line.appendChild(sibling);
1327 if (remainder.length) {
1328 sibling = document.createElement('span');
1329 sibling.className = oldColor;
1330 sibling.style.cssText = oldStyle;
1331 this.setTextContent(sibling, remainder);
1332 line.appendChild(sibling);
1337 span.className = color;
1338 span.style.cssText = style;
1340 // Overwrite (partial) <span> with new text
1341 s = s.substr(0, x - xPos) +
1343 s.substr(x + text.length - xPos);
1345 this.setTextContent(span, s);
1348 // Delete all subsequent <span>'s that have just been overwritten
1349 sibling = span.nextSibling;
1350 while (del > 0 && sibling) {
1351 s = this.getTextContent(sibling);
1354 line.removeChild(sibling);
1356 sibling = span.nextSibling;
1358 this.setTextContent(sibling, s.substr(del));
1363 // Merge <span> with next sibling, if styles are identical
1364 if (sibling && span.className == sibling.className &&
1365 span.style.cssText == sibling.style.cssText) {
1366 this.setTextContent(span,
1367 this.getTextContent(span) +
1368 this.getTextContent(sibling));
1369 line.removeChild(sibling);
1375 this.cursorX = x + text.length;
1376 if (this.cursorX >= this.terminalWidth) {
1377 this.cursorX = this.terminalWidth - 1;
1378 if (this.cursorX < 0) {
1384 if (!this.cursor.style.visibility) {
1385 var idx = this.cursorX - xPos;
1387 // If we are in a non-empty line, take the cursor Y position from the
1388 // other elements in this line. If dealing with broken, non-proportional
1389 // fonts, this is likely to yield better results.
1390 pixelY = span.offsetTop +
1391 span.offsetParent.offsetTop;
1392 s = this.getTextContent(span);
1393 var nxtIdx = idx - s.length;
1395 this.setTextContent(this.cursor, s.charAt(idx));
1396 pixelX = span.offsetLeft +
1397 idx*span.offsetWidth / s.length;
1400 pixelX = span.offsetLeft + span.offsetWidth;
1402 if (span.nextSibling) {
1403 s = this.getTextContent(span.nextSibling);
1404 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1406 pixelX = span.nextSibling.offsetLeft +
1407 nxtIdx*span.offsetWidth / s.length;
1410 this.setTextContent(this.cursor, ' ');
1414 this.setTextContent(this.cursor, ' ');
1418 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
1420 this.setTextContent(this.space, this.spaces(this.cursorX));
1421 this.cursor.style.left = this.space.offsetWidth +
1422 console.offsetLeft + 'px';
1424 this.cursorY = yIdx - this.numScrollbackLines;
1426 this.cursor.style.top = pixelY + 'px';
1428 this.cursor.style.top = yIdx*this.cursorHeight +
1429 console.offsetTop + 'px';
1433 // Merge <span> with previous sibling, if styles are identical
1434 if ((sibling = span.previousSibling) &&
1435 span.className == sibling.className &&
1436 span.style.cssText == sibling.style.cssText) {
1437 this.setTextContent(span,
1438 this.getTextContent(sibling) +
1439 this.getTextContent(span));
1440 line.removeChild(sibling);
1443 // Prune white space from the end of the current line
1444 span = line.lastChild;
1446 span.className == 'ansi0 bgAnsi15' &&
1447 !span.style.cssText.length) {
1448 // Scan backwards looking for first non-space character
1449 s = this.getTextContent(span);
1450 for (var i = s.length; i--; ) {
1451 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1452 if (i+1 != s.length) {
1453 this.setTextContent(s.substr(0, i+1));
1461 span = span.previousSibling;
1463 // Remove blank <span>'s from end of line
1464 line.removeChild(sibling);
1466 // Remove entire line (i.e. <div>), if empty
1467 var blank = document.createElement('pre');
1468 blank.style.height = this.cursorHeight + 'px';
1469 this.setTextContent(blank, '\n');
1470 line.parentNode.replaceChild(blank, line);
1477 VT100.prototype.gotoXY = function(x, y) {
1478 if (x >= this.terminalWidth) {
1479 x = this.terminalWidth - 1;
1485 if (this.offsetMode) {
1490 maxY = this.terminalHeight;
1498 this.putString(x, y, '', undefined);
1499 this.needWrap = false;
1502 VT100.prototype.gotoXaY = function(x, y) {
1503 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1506 VT100.prototype.refreshInvertedState = function() {
1507 if (this.isInverted) {
1508 this.scrollable.className += ' inverted';
1510 this.scrollable.className = this.scrollable.className.
1511 replace(/ *inverted/, '');
1515 VT100.prototype.enableAlternateScreen = function(state) {
1516 // Don't do anything, if we are already on the desired screen
1517 if ((state ? 1 : 0) == this.currentScreen) {
1518 // Calling the resizer is not actually necessary. But it is a good way
1519 // of resetting state that might have gotten corrupted.
1524 // We save the full state of the normal screen, when we switch away from it.
1525 // But for the alternate screen, no saving is necessary. We always reset
1526 // it when we switch to it.
1531 // Display new screen, and initialize state (the resizer does that for us).
1532 this.currentScreen = state ? 1 : 0;
1533 this.console[1-this.currentScreen].style.display = 'none';
1534 this.console[this.currentScreen].style.display = '';
1537 // If we switched to the alternate screen, reset it completely. Otherwise,
1538 // restore the saved state.
1541 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1543 this.restoreCursor();
1547 VT100.prototype.hideCursor = function() {
1548 var hidden = this.cursor.style.visibility == 'hidden';
1550 this.cursor.style.visibility = 'hidden';
1556 VT100.prototype.showCursor = function(x, y) {
1557 if (this.cursor.style.visibility) {
1558 this.cursor.style.visibility = '';
1559 this.putString(x == undefined ? this.cursorX : x,
1560 y == undefined ? this.cursorY : y,
1567 VT100.prototype.scrollBack = function() {
1568 var i = this.scrollable.scrollTop -
1569 this.scrollable.clientHeight;
1570 this.scrollable.scrollTop = i < 0 ? 0 : i;
1573 VT100.prototype.scrollFore = function() {
1574 var i = this.scrollable.scrollTop +
1575 this.scrollable.clientHeight;
1576 this.scrollable.scrollTop = i > this.numScrollbackLines *
1577 this.cursorHeight + 1
1578 ? this.numScrollbackLines *
1579 this.cursorHeight + 1
1583 VT100.prototype.spaces = function(i) {
1591 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
1596 if (w > this.terminalWidth) {
1597 w = this.terminalWidth;
1599 if ((w -= x) <= 0) {
1606 if (h > this.terminalHeight) {
1607 h = this.terminalHeight;
1609 if ((h -= y) <= 0) {
1613 // Special case the situation where we clear the entire screen, and we do
1614 // not have a scrollback buffer. In that case, we should just remove all
1616 if (!this.numScrollbackLines &&
1617 w == this.terminalWidth && h == this.terminalHeight &&
1618 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
1619 var console = this.console[this.currentScreen];
1620 while (console.lastChild) {
1621 console.removeChild(console.lastChild);
1623 this.putString(this.cursorX, this.cursorY, '', undefined);
1625 var hidden = this.hideCursor();
1626 var cx = this.cursorX;
1627 var cy = this.cursorY;
1628 var s = this.spaces(w);
1629 for (var i = y+h; i-- > y; ) {
1630 this.putString(x, i, s, color, style);
1632 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1636 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1638 var className = [ ];
1640 var console = this.console[this.currentScreen];
1641 if (sY >= console.childNodes.length) {
1642 text[0] = this.spaces(w);
1643 className[0] = undefined;
1644 style[0] = undefined;
1646 var line = console.childNodes[sY];
1647 if (line.tagName != 'DIV' || !line.childNodes.length) {
1648 text[0] = this.spaces(w);
1649 className[0] = undefined;
1650 style[0] = undefined;
1653 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1654 var s = this.getTextContent(span);
1657 var o = sX > x ? sX - x : 0;
1658 text[text.length] = s.substr(o, w);
1659 className[className.length] = span.className;
1660 style[style.length] = span.style.cssText;
1666 text[text.length] = this.spaces(w);
1667 className[className.length] = undefined;
1668 style[style.length] = undefined;
1672 var hidden = this.hideCursor();
1673 var cx = this.cursorX;
1674 var cy = this.cursorY;
1675 for (var i = 0; i < text.length; i++) {
1678 color = className[i];
1680 color = 'ansi0 bgAnsi15';
1682 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
1683 dX += text[i].length;
1685 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1688 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
1690 var left = incX < 0 ? -incX : 0;
1691 var right = incX > 0 ? incX : 0;
1692 var up = incY < 0 ? -incY : 0;
1693 var down = incY > 0 ? incY : 0;
1695 // Clip region against terminal size
1696 var dontScroll = null;
1701 if (w > this.terminalWidth - right) {
1702 w = this.terminalWidth - right;
1704 if ((w -= x) <= 0) {
1711 if (h > this.terminalHeight - down) {
1712 h = this.terminalHeight - down;
1718 if (style && style.indexOf('underline')) {
1719 // Different terminal emulators disagree on the attributes that
1720 // are used for scrolling. The consensus seems to be, never to
1721 // fill with underlined spaces. N.B. this is different from the
1722 // cases when the user blanks a region. User-initiated blanking
1723 // always fills with all of the current attributes.
1724 style = style.replace(/text-decoration:underline;/, '');
1727 // Compute current scroll position
1728 var scrollPos = this.numScrollbackLines -
1729 (this.scrollable.scrollTop-1) / this.cursorHeight;
1731 // Determine original cursor position. Hide cursor temporarily to avoid
1732 // visual artifacts.
1733 var hidden = this.hideCursor();
1734 var cx = this.cursorX;
1735 var cy = this.cursorY;
1736 var console = this.console[this.currentScreen];
1738 if (!incX && !x && w == this.terminalWidth) {
1739 // Scrolling entire lines
1742 if (!this.currentScreen && y == -incY &&
1743 h == this.terminalHeight + incY) {
1744 // Scrolling up with adding to the scrollback buffer. This is only
1745 // possible if there are at least as many lines in the console,
1746 // as the terminal is high
1747 while (console.childNodes.length < this.terminalHeight) {
1748 this.insertBlankLine(this.terminalHeight);
1751 // Add new lines at bottom in order to force scrolling
1752 for (var i = 0; i < y; i++) {
1753 this.insertBlankLine(console.childNodes.length, color, style);
1756 // Adjust the number of lines in the scrollback buffer by
1757 // removing excess entries.
1758 this.updateNumScrollbackLines();
1759 while (this.numScrollbackLines >
1760 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1761 console.removeChild(console.firstChild);
1762 this.numScrollbackLines--;
1765 // Mark lines in the scrollback buffer, so that they do not get
1767 for (var i = this.numScrollbackLines, j = -incY;
1768 i-- > 0 && j-- > 0; ) {
1769 console.childNodes[i].className = 'scrollback';
1772 // Scrolling up without adding to the scrollback buffer.
1775 console.childNodes.length >
1776 this.numScrollbackLines + y + incY; ) {
1777 console.removeChild(console.childNodes[
1778 this.numScrollbackLines + y + incY]);
1781 // If we used to have a scrollback buffer, then we must make sure
1782 // that we add back blank lines at the bottom of the terminal.
1783 // Similarly, if we are scrolling in the middle of the screen,
1784 // we must add blank lines to ensure that the bottom of the screen
1785 // does not move up.
1786 if (this.numScrollbackLines > 0 ||
1787 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1788 for (var i = -incY; i-- > 0; ) {
1789 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1798 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1799 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1801 for (var i = incY; i--; ) {
1802 this.insertBlankLine(this.numScrollbackLines + y, color, style);
1806 // Scrolling partial lines
1808 // Scrolling up or horizontally within a line
1809 for (var i = y + this.numScrollbackLines;
1810 i < y + this.numScrollbackLines + h;
1812 this.copyLineSegment(x + incX, i + incY, x, i, w);
1816 for (var i = y + this.numScrollbackLines + h;
1817 i-- > y + this.numScrollbackLines; ) {
1818 this.copyLineSegment(x + incX, i + incY, x, i, w);
1822 // Clear blank regions
1824 this.clearRegion(x, y, incX, h, color, style);
1825 } else if (incX < 0) {
1826 this.clearRegion(x + w + incX, y, -incX, h, color, style);
1829 this.clearRegion(x, y, w, incY, color, style);
1830 } else if (incY < 0) {
1831 this.clearRegion(x, y + h + incY, w, -incY, color, style);
1835 // Reset scroll position
1836 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1837 this.cursorHeight + 1;
1839 // Move cursor back to its original position
1840 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1844 VT100.prototype.copy = function(selection) {
1845 if (selection == undefined) {
1846 selection = this.selection();
1848 this.internalClipboard = undefined;
1849 if (selection.length) {
1852 this.cliphelper.value = selection;
1853 this.cliphelper.select();
1854 this.cliphelper.createTextRange().execCommand('copy');
1856 this.internalClipboard = selection;
1858 this.cliphelper.value = '';
1862 VT100.prototype.copyLast = function() {
1863 // Opening the context menu can remove the selection. We try to prevent this
1864 // from happening, but that is not possible for all browsers. So, instead,
1865 // we compute the selection before showing the menu.
1866 this.copy(this.lastSelection);
1869 VT100.prototype.pasteFnc = function() {
1870 var clipboard = undefined;
1871 if (this.internalClipboard != undefined) {
1872 clipboard = this.internalClipboard;
1875 this.cliphelper.value = '';
1876 this.cliphelper.createTextRange().execCommand('paste');
1877 clipboard = this.cliphelper.value;
1881 this.cliphelper.value = '';
1882 if (clipboard && this.menu.style.visibility == 'hidden') {
1884 this.keysPressed('' + clipboard);
1891 VT100.prototype.toggleUTF = function() {
1892 this.utfEnabled = !this.utfEnabled;
1894 // We always persist the last value that the user selected. Not necessarily
1895 // the last value that a random program requested.
1896 this.utfPreferred = this.utfEnabled;
1899 VT100.prototype.toggleBell = function() {
1900 this.visualBell = !this.visualBell;
1903 VT100.prototype.about = function() {
1904 alert("VT100 Terminal Emulator " + "2.9 (revision 183)" +
1905 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1906 "For more information check http://shellinabox.com");
1909 VT100.prototype.hideContextMenu = function() {
1910 this.menu.style.visibility = 'hidden';
1911 this.menu.style.top = '-100px';
1912 this.menu.style.left = '-100px';
1913 this.menu.style.width = '0px';
1914 this.menu.style.height = '0px';
1917 VT100.prototype.extendContextMenu = function(entries, actions) {
1920 VT100.prototype.showContextMenu = function(x, y) {
1921 this.menu.innerHTML =
1922 '<table class="popup" ' +
1923 'cellpadding="0" cellspacing="0">' +
1925 '<ul id="menuentries">' +
1926 '<li id="beginclipboard">Copy</li>' +
1927 '<li id="endclipboard">Paste</li>' +
1929 '<li id="reset">Reset</li>' +
1931 '<li id="beginconfig">' +
1932 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
1934 '<li id="endconfig">' +
1935 (this.visualBell ? '<img src="enabled.gif" />' : '') +
1937 (this.usercss.firstChild ?
1938 '<hr id="beginusercss" />' +
1939 this.usercss.innerHTML +
1940 '<hr id="endusercss" />' :
1942 '<li id="about">About...</li>' +
1947 var popup = this.menu.firstChild;
1948 var menuentries = this.getChildById(popup, 'menuentries');
1950 // Determine menu entries that should be disabled
1951 this.lastSelection = this.selection();
1952 if (!this.lastSelection.length) {
1953 menuentries.firstChild.className
1956 var p = this.pasteFnc();
1958 menuentries.childNodes[1].className
1962 // Actions for default items
1963 var actions = [ this.copyLast, p, this.reset,
1964 this.toggleUTF, this.toggleBell ];
1966 // Actions for user CSS styles (if any)
1967 for (var i = 0; i < this.usercssActions.length; ++i) {
1968 actions[actions.length] = this.usercssActions[i];
1970 actions[actions.length] = this.about;
1972 // Allow subclasses to dynamically add entries to the context menu
1973 this.extendContextMenu(menuentries, actions);
1975 // Hook up event listeners
1976 for (var node = menuentries.firstChild, i = 0; node;
1977 node = node.nextSibling) {
1978 if (node.tagName == 'LI') {
1979 if (node.className != 'disabled') {
1980 this.addListener(node, 'mouseover',
1981 function(vt100, node) {
1983 node.className = 'hover';
1986 this.addListener(node, 'mouseout',
1987 function(vt100, node) {
1989 node.className = '';
1992 this.addListener(node, 'mousedown',
1993 function(vt100, action) {
1994 return function(event) {
1995 vt100.hideContextMenu();
1997 vt100.storeUserSettings();
1998 return vt100.cancelEvent(event || window.event);
2000 }(this, actions[i]));
2001 this.addListener(node, 'mouseup',
2003 return function(event) {
2004 return vt100.cancelEvent(event || window.event);
2007 this.addListener(node, 'mouseclick',
2009 return function(event) {
2010 return vt100.cancelEvent(event || window.event);
2018 // Position menu next to the mouse pointer
2019 if (x + popup.clientWidth > this.container.offsetWidth) {
2020 x = this.container.offsetWidth - popup.clientWidth;
2025 if (y + popup.clientHeight > this.container.offsetHeight) {
2026 y = this.container.offsetHeight-popup.clientHeight;
2031 popup.style.left = x + 'px';
2032 popup.style.top = y + 'px';
2034 // Block all other interactions with the terminal emulator
2035 this.menu.style.left = '0px';
2036 this.menu.style.top = '0px';
2037 this.menu.style.width = this.container.offsetWidth + 'px';
2038 this.menu.style.height = this.container.offsetHeight + 'px';
2039 this.addListener(this.menu, 'click', function(vt100) {
2041 vt100.hideContextMenu();
2046 this.menu.style.visibility = '';
2049 VT100.prototype.keysPressed = function(ch) {
2050 for (var i = 0; i < ch.length; i++) {
2051 var c = ch.charCodeAt(i);
2052 this.vt100(c >= 7 && c <= 15 ||
2053 c == 24 || c == 26 || c == 27 || c >= 32
2054 ? String.fromCharCode(c) : '<' + c + '>');
2058 VT100.prototype.applyModifiers = function(ch, event) {
2060 if (event.ctrlKey) {
2061 if (ch >= 32 && ch <= 127) {
2062 // For historic reasons, some control characters are treated specially
2064 case /* 3 */ 51: ch = 27; break;
2065 case /* 4 */ 52: ch = 28; break;
2066 case /* 5 */ 53: ch = 29; break;
2067 case /* 6 */ 54: ch = 30; break;
2068 case /* 7 */ 55: ch = 31; break;
2069 case /* 8 */ 56: ch = 127; break;
2070 case /* ? */ 63: ch = 127; break;
2071 default: ch &= 31; break;
2075 return String.fromCharCode(ch);
2081 VT100.prototype.handleKey = function(event) {
2082 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2083 // (event.shiftKey || event.ctrlKey || event.altKey ||
2084 // event.metaKey ? ', ' +
2085 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2086 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2089 if (typeof event.charCode != 'undefined') {
2090 // non-IE keypress events have a translated charCode value. Also, our
2091 // fake events generated when receiving keydown events include this data
2093 ch = event.charCode;
2094 key = event.keyCode;
2096 // When sending a keypress event, IE includes the translated character
2097 // code in the keyCode field.
2102 // Apply modifier keys (ctrl and shift)
2106 ch = this.applyModifiers(ch, event);
2108 // By this point, "ch" is either defined and contains the character code, or
2109 // it is undefined and "key" defines the code of a function key
2110 if (ch != undefined) {
2111 this.scrollable.scrollTop = this.numScrollbackLines *
2112 this.cursorHeight + 1;
2114 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2115 // Many programs have difficulties dealing with parametrized escape
2116 // sequences for function keys. Thus, if ALT is the only modifier
2117 // key, return Emacs-style keycodes for commonly used keys.
2119 case 33: /* Page Up */ ch = '\u001B<'; break;
2120 case 34: /* Page Down */ ch = '\u001B>'; break;
2121 case 37: /* Left */ ch = '\u001Bb'; break;
2122 case 38: /* Up */ ch = '\u001Bp'; break;
2123 case 39: /* Right */ ch = '\u001Bf'; break;
2124 case 40: /* Down */ ch = '\u001Bn'; break;
2125 case 46: /* Delete */ ch = '\u001Bd'; break;
2128 } else if (event.shiftKey && !event.ctrlKey &&
2129 !event.altKey && !event.metaKey) {
2131 case 33: /* Page Up */ this.scrollBack(); return;
2132 case 34: /* Page Down */ this.scrollFore(); return;
2136 if (ch == undefined) {
2138 case 8: /* Backspace */ ch = '\u007f'; break;
2139 case 9: /* Tab */ ch = '\u0009'; break;
2140 case 10: /* Return */ ch = '\u000A'; break;
2141 case 13: /* Enter */ ch = this.crLfMode ?
2142 '\r\n' : '\r'; break;
2143 case 16: /* Shift */ return;
2144 case 17: /* Ctrl */ return;
2145 case 18: /* Alt */ return;
2146 case 19: /* Break */ return;
2147 case 20: /* Caps Lock */ return;
2148 case 27: /* Escape */ ch = '\u001B'; break;
2149 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2150 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2151 case 35: /* End */ ch = '\u001BOF'; break;
2152 case 36: /* Home */ ch = '\u001BOH'; break;
2153 case 37: /* Left */ ch = this.cursorKeyMode ?
2154 '\u001BOD' : '\u001B[D'; break;
2155 case 38: /* Up */ ch = this.cursorKeyMode ?
2156 '\u001BOA' : '\u001B[A'; break;
2157 case 39: /* Right */ ch = this.cursorKeyMode ?
2158 '\u001BOC' : '\u001B[C'; break;
2159 case 40: /* Down */ ch = this.cursorKeyMode ?
2160 '\u001BOB' : '\u001B[B'; break;
2161 case 45: /* Insert */ ch = '\u001B[2~'; break;
2162 case 46: /* Delete */ ch = '\u001B[3~'; break;
2163 case 91: /* Left Window */ return;
2164 case 92: /* Right Window */ return;
2165 case 93: /* Select */ return;
2166 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2167 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2168 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2169 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2170 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2171 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2172 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2173 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2174 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2175 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2176 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2177 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2178 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2179 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2180 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2181 case 112: /* F1 */ ch = '\u001BOP'; break;
2182 case 113: /* F2 */ ch = '\u001BOQ'; break;
2183 case 114: /* F3 */ ch = '\u001BOR'; break;
2184 case 115: /* F4 */ ch = '\u001BOS'; break;
2185 case 116: /* F5 */ ch = '\u001B[15~'; break;
2186 case 117: /* F6 */ ch = '\u001B[17~'; break;
2187 case 118: /* F7 */ ch = '\u001B[18~'; break;
2188 case 119: /* F8 */ ch = '\u001B[19~'; break;
2189 case 120: /* F9 */ ch = '\u001B[20~'; break;
2190 case 121: /* F10 */ ch = '\u001B[21~'; break;
2191 case 122: /* F11 */ ch = '\u001B[23~'; break;
2192 case 123: /* F12 */ ch = '\u001B[24~'; break;
2193 case 144: /* Num Lock */ return;
2194 case 145: /* Scroll Lock */ return;
2195 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2196 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2197 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2198 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2199 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2200 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2201 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2202 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2203 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2204 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2205 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2208 this.scrollable.scrollTop = this.numScrollbackLines *
2209 this.cursorHeight + 1;
2213 // "ch" now contains the sequence of keycodes to send. But we might still
2214 // have to apply the effects of modifier keys.
2215 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2216 var start, digit, part1, part2;
2217 if ((start = ch.substr(0, 2)) == '\u001B[') {
2219 part1.length < ch.length &&
2220 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2221 part1 = ch.substr(0, part1.length + 1);
2223 part2 = ch.substr(part1.length);
2224 if (part1.length > 2) {
2227 } else if (start == '\u001BO') {
2229 part2 = ch.substr(2);
2231 if (part1 != undefined) {
2233 ((event.shiftKey ? 1 : 0) +
2234 (event.altKey|event.metaKey ? 2 : 0) +
2235 (event.ctrlKey ? 4 : 0)) +
2237 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2242 if (this.menu.style.visibility == 'hidden') {
2243 // this.vt100('R: c=');
2244 // for (var i = 0; i < ch.length; i++)
2245 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2246 // this.vt100('\r\n');
2247 this.keysPressed(ch);
2251 VT100.prototype.inspect = function(o, d) {
2252 if (d == undefined) {
2256 if (typeof o == 'object' && ++d < 2) {
2259 rc += this.spaces(d * 2) + i + ' -> ';
2261 rc += this.inspect(o[i], d);
2263 rc += '?' + '?' + '?\r\n';
2268 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2273 VT100.prototype.checkComposedKeys = function(event) {
2274 // Composed keys (at least on Linux) do not generate normal events.
2275 // Instead, they get entered into the text field. We normally catch
2276 // this on the next keyup event.
2277 var s = this.input.value;
2279 this.input.value = '';
2280 if (this.menu.style.visibility == 'hidden') {
2281 this.keysPressed(s);
2286 VT100.prototype.fixEvent = function(event) {
2287 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2288 // is used as a second-level selector, clear the modifier bits before
2289 // handling the event.
2290 if (event.ctrlKey && event.altKey) {
2292 fake.charCode = event.charCode;
2293 fake.keyCode = event.keyCode;
2294 fake.ctrlKey = false;
2295 fake.shiftKey = event.shiftKey;
2296 fake.altKey = false;
2297 fake.metaKey = event.metaKey;
2301 // Some browsers fail to translate keys, if both shift and alt/meta is
2302 // pressed at the same time. We try to translate those cases, but that
2303 // only works for US keyboard layouts.
2304 if (event.shiftKey) {
2307 switch (this.lastNormalKeyDownEvent.keyCode) {
2308 case 39: /* ' -> " */ u = 39; s = 34; break;
2309 case 44: /* , -> < */ u = 44; s = 60; break;
2310 case 45: /* - -> _ */ u = 45; s = 95; break;
2311 case 46: /* . -> > */ u = 46; s = 62; break;
2312 case 47: /* / -> ? */ u = 47; s = 63; break;
2314 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2315 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2316 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2317 case 51: /* 3 -> # */ u = 51; s = 35; break;
2318 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2319 case 53: /* 5 -> % */ u = 53; s = 37; break;
2320 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2321 case 55: /* 7 -> & */ u = 55; s = 38; break;
2322 case 56: /* 8 -> * */ u = 56; s = 42; break;
2323 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2325 case 59: /* ; -> : */ u = 59; s = 58; break;
2326 case 61: /* = -> + */ u = 61; s = 43; break;
2327 case 91: /* [ -> { */ u = 91; s = 123; break;
2328 case 92: /* \ -> | */ u = 92; s = 124; break;
2329 case 93: /* ] -> } */ u = 93; s = 125; break;
2330 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2332 case 109: /* - -> _ */ u = 45; s = 95; break;
2333 case 111: /* / -> ? */ u = 47; s = 63; break;
2335 case 186: /* ; -> : */ u = 59; s = 58; break;
2336 case 187: /* = -> + */ u = 61; s = 43; break;
2337 case 188: /* , -> < */ u = 44; s = 60; break;
2338 case 189: /* - -> _ */ u = 45; s = 95; break;
2339 case 190: /* . -> > */ u = 46; s = 62; break;
2340 case 191: /* / -> ? */ u = 47; s = 63; break;
2341 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2342 case 219: /* [ -> { */ u = 91; s = 123; break;
2343 case 220: /* \ -> | */ u = 92; s = 124; break;
2344 case 221: /* ] -> } */ u = 93; s = 125; break;
2345 case 222: /* ' -> " */ u = 39; s = 34; break;
2348 if (s && (event.charCode == u || event.charCode == 0)) {
2351 fake.keyCode = event.keyCode;
2352 fake.ctrlKey = event.ctrlKey;
2353 fake.shiftKey = event.shiftKey;
2354 fake.altKey = event.altKey;
2355 fake.metaKey = event.metaKey;
2362 VT100.prototype.keyDown = function(event) {
2363 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2364 // (event.shiftKey || event.ctrlKey || event.altKey ||
2365 // event.metaKey ? ', ' +
2366 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2367 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2369 this.checkComposedKeys(event);
2370 this.lastKeyPressedEvent = undefined;
2371 this.lastKeyDownEvent = undefined;
2372 this.lastNormalKeyDownEvent = event;
2375 event.keyCode == 32 ||
2376 event.keyCode >= 48 && event.keyCode <= 57 ||
2377 event.keyCode >= 65 && event.keyCode <= 90;
2380 event.keyCode >= 96 && event.keyCode <= 105 ||
2381 event.keyCode == 226;
2384 event.keyCode == 59 || event.keyCode == 61 ||
2385 event.keyCode == 106 || event.keyCode == 107 ||
2386 event.keyCode >= 109 && event.keyCode <= 111 ||
2387 event.keyCode >= 186 && event.keyCode <= 192 ||
2388 event.keyCode >= 219 && event.keyCode <= 222 ||
2389 event.keyCode == 252;
2391 if (navigator.appName == 'Konqueror') {
2392 normalKey |= event.keyCode < 128;
2397 // We normally prefer to look at keypress events, as they perform the
2398 // translation from keyCode to charCode. This is important, as the
2399 // translation is locale-dependent.
2400 // But for some keys, we must intercept them during the keydown event,
2401 // as they would otherwise get interpreted by the browser.
2402 // Even, when doing all of this, there are some keys that we can never
2403 // intercept. This applies to some of the menu navigation keys in IE.
2404 // In fact, we see them, but we cannot stop IE from seeing them, too.
2405 if ((event.charCode || event.keyCode) &&
2406 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2408 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2409 // interpret this sequence ourselves, as some keyboard layouts use
2410 // it for second-level layouts.
2411 !(event.ctrlKey && event.altKey)) ||
2412 this.catchModifiersEarly && normalKey && !alphNumKey &&
2413 (event.ctrlKey || event.altKey || event.metaKey) ||
2415 this.lastKeyDownEvent = event;
2417 fake.ctrlKey = event.ctrlKey;
2418 fake.shiftKey = event.shiftKey;
2419 fake.altKey = event.altKey;
2420 fake.metaKey = event.metaKey;
2422 fake.charCode = event.keyCode;
2426 fake.keyCode = event.keyCode;
2427 if (!alphNumKey && event.shiftKey) {
2428 fake = this.fixEvent(fake);
2432 this.handleKey(fake);
2433 this.lastNormalKeyDownEvent = undefined;
2436 // For non-IE browsers
2437 event.stopPropagation();
2438 event.preventDefault();
2443 event.cancelBubble = true;
2444 event.returnValue = false;
2454 VT100.prototype.keyPressed = function(event) {
2455 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2456 // (event.shiftKey || event.ctrlKey || event.altKey ||
2457 // event.metaKey ? ', ' +
2458 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2459 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2461 if (this.lastKeyDownEvent) {
2462 // If we already processed the key on keydown, do not process it
2463 // again here. Ideally, the browser should not even have generated a
2464 // keypress event in this case. But that does not appear to always work.
2465 this.lastKeyDownEvent = undefined;
2467 this.handleKey(event.altKey || event.metaKey
2468 ? this.fixEvent(event) : event);
2472 // For non-IE browsers
2473 event.preventDefault();
2479 event.cancelBubble = true;
2480 event.returnValue = false;
2485 this.lastNormalKeyDownEvent = undefined;
2486 this.lastKeyPressedEvent = event;
2490 VT100.prototype.keyUp = function(event) {
2491 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2492 // (event.shiftKey || event.ctrlKey || event.altKey ||
2493 // event.metaKey ? ', ' +
2494 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2495 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2497 if (this.lastKeyPressedEvent) {
2498 // The compose key on Linux occasionally confuses the browser and keeps
2499 // inserting bogus characters into the input field, even if just a regular
2500 // key has been pressed. Detect this case and drop the bogus characters.
2502 event.srcElement).value = '';
2504 // This is usually were we notice that a key has been composed and
2505 // thus failed to generate normal events.
2506 this.checkComposedKeys(event);
2508 // Some browsers don't report keypress events if ctrl or alt is pressed
2509 // for non-alphanumerical keys. Patch things up for now, but in the
2510 // future we will catch these keys earlier (in the keydown handler).
2511 if (this.lastNormalKeyDownEvent) {
2512 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
2513 this.catchModifiersEarly = true;
2515 event.keyCode == 32 ||
2516 event.keyCode >= 48 && event.keyCode <= 57 ||
2517 event.keyCode >= 65 && event.keyCode <= 90;
2520 event.keyCode >= 96 && event.keyCode <= 105;
2523 event.keyCode == 59 || event.keyCode == 61 ||
2524 event.keyCode == 106 || event.keyCode == 107 ||
2525 event.keyCode >= 109 && event.keyCode <= 111 ||
2526 event.keyCode >= 186 && event.keyCode <= 192 ||
2527 event.keyCode >= 219 && event.keyCode <= 222 ||
2528 event.keyCode == 252;
2530 fake.ctrlKey = event.ctrlKey;
2531 fake.shiftKey = event.shiftKey;
2532 fake.altKey = event.altKey;
2533 fake.metaKey = event.metaKey;
2535 fake.charCode = event.keyCode;
2539 fake.keyCode = event.keyCode;
2540 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2541 fake = this.fixEvent(fake);
2544 this.lastNormalKeyDownEvent = undefined;
2545 this.handleKey(fake);
2551 event.cancelBubble = true;
2552 event.returnValue = false;
2557 this.lastKeyDownEvent = undefined;
2558 this.lastKeyPressedEvent = undefined;
2562 VT100.prototype.animateCursor = function(inactive) {
2563 if (!this.cursorInterval) {
2564 this.cursorInterval = setInterval(
2567 vt100.animateCursor();
2569 // Use this opportunity to check whether the user entered a composed
2570 // key, or whether somebody pasted text into the textfield.
2571 vt100.checkComposedKeys();
2575 if (inactive != undefined || this.cursor.className != 'inactive') {
2577 this.cursor.className = 'inactive';
2579 this.cursor.className = this.cursor.className == 'bright'
2585 VT100.prototype.blurCursor = function() {
2586 this.animateCursor(true);
2589 VT100.prototype.focusCursor = function() {
2590 this.animateCursor(false);
2593 VT100.prototype.flashScreen = function() {
2594 this.isInverted = !this.isInverted;
2595 this.refreshInvertedState();
2596 this.isInverted = !this.isInverted;
2597 setTimeout(function(vt100) {
2599 vt100.refreshInvertedState();
2604 VT100.prototype.beep = function() {
2605 if (this.visualBell) {
2612 this.beeper.src = 'beep.wav';
2619 VT100.prototype.bs = function() {
2620 if (this.cursorX > 0) {
2621 this.gotoXY(this.cursorX - 1, this.cursorY);
2622 this.needWrap = false;
2626 VT100.prototype.ht = function(count) {
2627 if (count == undefined) {
2630 var cx = this.cursorX;
2631 while (count-- > 0) {
2632 while (cx++ < this.terminalWidth) {
2633 var tabState = this.userTabStop[cx];
2634 if (tabState == false) {
2635 // Explicitly cleared tab stop
2637 } else if (tabState) {
2638 // Explicitly set tab stop
2641 // Default tab stop at each eighth column
2648 if (cx > this.terminalWidth - 1) {
2649 cx = this.terminalWidth - 1;
2651 if (cx != this.cursorX) {
2652 this.gotoXY(cx, this.cursorY);
2656 VT100.prototype.rt = function(count) {
2657 if (count == undefined) {
2660 var cx = this.cursorX;
2661 while (count-- > 0) {
2663 var tabState = this.userTabStop[cx];
2664 if (tabState == false) {
2665 // Explicitly cleared tab stop
2667 } else if (tabState) {
2668 // Explicitly set tab stop
2671 // Default tab stop at each eighth column
2681 if (cx != this.cursorX) {
2682 this.gotoXY(cx, this.cursorY);
2686 VT100.prototype.cr = function() {
2687 this.gotoXY(0, this.cursorY);
2688 this.needWrap = false;
2691 VT100.prototype.lf = function(count) {
2692 if (count == undefined) {
2695 if (count > this.terminalHeight) {
2696 count = this.terminalHeight;
2702 while (count-- > 0) {
2703 if (this.cursorY == this.bottom - 1) {
2704 this.scrollRegion(0, this.top + 1,
2705 this.terminalWidth, this.bottom - this.top - 1,
2706 0, -1, this.color, this.style);
2708 } else if (this.cursorY < this.terminalHeight - 1) {
2709 this.gotoXY(this.cursorX, this.cursorY + 1);
2714 VT100.prototype.ri = function(count) {
2715 if (count == undefined) {
2718 if (count > this.terminalHeight) {
2719 count = this.terminalHeight;
2725 while (count-- > 0) {
2726 if (this.cursorY == this.top) {
2727 this.scrollRegion(0, this.top,
2728 this.terminalWidth, this.bottom - this.top - 1,
2729 0, 1, this.color, this.style);
2730 } else if (this.cursorY > 0) {
2731 this.gotoXY(this.cursorX, this.cursorY - 1);
2734 this.needWrap = false;
2737 VT100.prototype.respondID = function() {
2738 this.respondString += '\u001B[?6c';
2741 VT100.prototype.respondSecondaryDA = function() {
2742 this.respondString += '\u001B[>0;0;0c';
2746 VT100.prototype.updateStyle = function() {
2748 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2749 this.style = 'text-decoration:underline;';
2751 var bg = (this.attr >> 4) & 0xF;
2752 var fg = this.attr & 0xF;
2753 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2758 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2759 fg = 8; // Dark grey
2760 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2763 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2766 // Make some readability enhancements. Most notably, disallow identical
2767 // background and foreground colors.
2769 if ((fg ^= 8) == 7) {
2773 // And disallow bright colors on a light-grey background.
2774 if (bg == 7 && fg >= 8) {
2775 if ((fg -= 8) == 7) {
2780 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2783 VT100.prototype.setAttrColors = function(attr) {
2784 if (attr != this.attr) {
2790 VT100.prototype.saveCursor = function() {
2791 this.savedX[this.currentScreen] = this.cursorX;
2792 this.savedY[this.currentScreen] = this.cursorY;
2793 this.savedAttr[this.currentScreen] = this.attr;
2794 this.savedUseGMap = this.useGMap;
2795 for (var i = 0; i < 4; i++) {
2796 this.savedGMap[i] = this.GMap[i];
2798 this.savedValid[this.currentScreen] = true;
2801 VT100.prototype.restoreCursor = function() {
2802 if (!this.savedValid[this.currentScreen]) {
2805 this.attr = this.savedAttr[this.currentScreen];
2807 this.useGMap = this.savedUseGMap;
2808 for (var i = 0; i < 4; i++) {
2809 this.GMap[i] = this.savedGMap[i];
2811 this.translate = this.GMap[this.useGMap];
2812 this.needWrap = false;
2813 this.gotoXY(this.savedX[this.currentScreen],
2814 this.savedY[this.currentScreen]);
2817 VT100.prototype.setMode = function(state) {
2818 for (var i = 0; i <= this.npar; i++) {
2819 if (this.isQuestionMark) {
2820 switch (this.par[i]) {
2821 case 1: this.cursorKeyMode = state; break;
2822 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2823 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2824 case 6: this.offsetMode = state; break;
2825 case 7: this.autoWrapMode = state; break;
2827 case 9: this.mouseReporting = state; break;
2828 case 25: this.cursorNeedsShowing = state;
2829 if (state) { this.showCursor(); }
2830 else { this.hideCursor(); } break;
2833 case 47: this.enableAlternateScreen(state); break;
2837 switch (this.par[i]) {
2838 case 3: this.dispCtrl = state; break;
2839 case 4: this.insertMode = state; break;
2840 case 20:this.crLfMode = state; break;
2847 VT100.prototype.statusReport = function() {
2848 // Ready and operational.
2849 this.respondString += '\u001B[0n';
2852 VT100.prototype.cursorReport = function() {
2853 this.respondString += '\u001B[' +
2854 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2856 (this.cursorX + 1) +
2860 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2861 // Changing of cursor color is not implemented.
2864 VT100.prototype.openPrinterWindow = function() {
2867 if (!this.printWin || this.printWin.closed) {
2868 this.printWin = window.open('', 'print-output',
2869 'width=800,height=600,directories=no,location=no,menubar=yes,' +
2870 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
2871 this.printWin.document.body.innerHTML =
2872 '<link rel="stylesheet" href="' +
2873 document.location.protocol + '//' + document.location.host +
2874 document.location.pathname.replace(/[^/]*$/, '') +
2875 'print-styles.css" type="text/css">\n' +
2876 '<div id="options"><input id="autoprint" type="checkbox"' +
2877 (this.autoprint ? ' checked' : '') + '>' +
2878 'Automatically, print page(s) when job is ready' +
2879 '</input></div>\n' +
2880 '<div id="spacer"><input type="checkbox"> </input></div>' +
2881 '<pre id="print"></pre>\n';
2882 var autoprint = this.printWin.document.getElementById('autoprint');
2883 this.addListener(autoprint, 'click',
2884 (function(vt100, autoprint) {
2886 vt100.autoprint = autoprint.checked;
2887 vt100.storeUserSettings();
2890 })(this, autoprint));
2891 this.printWin.document.title = 'ShellInABox Printer Output';
2894 // Maybe, a popup blocker prevented us from working. Better catch the
2895 // exception, so that we won't break the entire terminal session. The
2896 // user probably needs to disable the blocker first before retrying the
2900 rc &= this.printWin && !this.printWin.closed &&
2901 (this.printWin.innerWidth ||
2902 this.printWin.document.documentElement.clientWidth ||
2903 this.printWin.document.body.clientWidth) > 1;
2905 if (!rc && this.printing == 100) {
2906 // Different popup blockers work differently. We try to detect a couple
2907 // of common methods. And then we retry again a brief amount later, as
2908 // false positives are otherwise possible. If we are sure that there is
2909 // a popup blocker in effect, we alert the user to it. This is helpful
2910 // as some popup blockers have minimal or no UI, and the user might not
2911 // notice that they are missing the popup. In any case, we only show at
2912 // most one message per print job.
2913 this.printing = true;
2914 setTimeout((function(win) {
2916 if (!win || win.closed ||
2918 win.document.documentElement.clientWidth ||
2919 win.document.body.clientWidth) <= 1) {
2920 alert('Attempted to print, but a popup blocker ' +
2921 'prevented the printer window from opening');
2924 })(this.printWin), 2000);
2929 VT100.prototype.sendToPrinter = function(s) {
2930 this.openPrinterWindow();
2932 var doc = this.printWin.document;
2933 var print = doc.getElementById('print');
2934 if (print.lastChild && print.lastChild.nodeName == '#text') {
2935 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
2937 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
2940 // There probably was a more aggressive popup blocker that prevented us
2941 // from accessing the printer windows.
2945 VT100.prototype.sendControlToPrinter = function(ch) {
2946 // We get called whenever doControl() is active. But for the printer, we
2947 // only implement a basic line printer that doesn't understand most of
2948 // the escape sequences of the VT100 terminal. In fact, the only escape
2949 // sequence that we really need to recognize is '^[[5i' for turning the
2955 this.openPrinterWindow();
2956 var doc = this.printWin.document;
2957 var print = doc.getElementById('print');
2958 var chars = print.lastChild &&
2959 print.lastChild.nodeName == '#text' ?
2960 print.lastChild.textContent.length : 0;
2961 this.sendToPrinter(this.spaces(8 - (chars % 8)));
2968 this.openPrinterWindow();
2969 var pageBreak = this.printWin.document.createElement('div');
2970 pageBreak.className = 'pagebreak';
2971 pageBreak.innerHTML = '<hr />';
2972 this.printWin.document.getElementById('print').appendChild(pageBreak);
2976 this.openPrinterWindow();
2977 var lineBreak = this.printWin.document.createElement('br');
2978 this.printWin.document.getElementById('print').appendChild(lineBreak);
2982 this.isEsc = 1 /* ESesc */;
2985 switch (this.isEsc) {
2987 this.isEsc = 0 /* ESnormal */;
2990 this.isEsc = 2 /* ESsquare */;
2996 case 2 /* ESsquare */:
2998 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
2999 0, 0, 0, 0, 0, 0, 0, 0 ];
3000 this.isEsc = 3 /* ESgetpars */;
3001 this.isQuestionMark = ch == 0x3F /*?*/;
3002 if (this.isQuestionMark) {
3006 case 3 /* ESgetpars */:
3007 if (ch == 0x3B /*;*/) {
3010 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3011 var par = this.par[this.npar];
3012 if (par == undefined) {
3015 this.par[this.npar] = 10*par + (ch & 0xF);
3018 this.isEsc = 4 /* ESgotpars */;
3021 case 4 /* ESgotpars */:
3022 this.isEsc = 0 /* ESnormal */;
3023 if (this.isQuestionMark) {
3028 this.csii(this.par[0]);
3035 this.isEsc = 0 /* ESnormal */;
3041 // There probably was a more aggressive popup blocker that prevented us
3042 // from accessing the printer windows.
3046 VT100.prototype.csiAt = function(number) {
3051 if (number > this.terminalWidth - this.cursorX) {
3052 number = this.terminalWidth - this.cursorX;
3054 this.scrollRegion(this.cursorX, this.cursorY,
3055 this.terminalWidth - this.cursorX - number, 1,
3056 number, 0, this.color, this.style);
3057 this.needWrap = false;
3060 VT100.prototype.csii = function(number) {
3063 case 0: // Print Screen
3066 case 4: // Stop printing
3068 if (this.printing && this.printWin && !this.printWin.closed) {
3069 var print = this.printWin.document.getElementById('print');
3070 while (print.lastChild &&
3071 print.lastChild.tagName == 'DIV' &&
3072 print.lastChild.className == 'pagebreak') {
3073 // Remove trailing blank pages
3074 print.removeChild(print.lastChild);
3076 if (this.autoprint) {
3077 this.printWin.print();
3082 this.printing = false;
3084 case 5: // Start printing
3085 if (!this.printing && this.printWin && !this.printWin.closed) {
3086 this.printWin.document.getElementById('print').innerHTML = '';
3088 this.printing = 100;
3095 VT100.prototype.csiJ = function(number) {
3097 case 0: // Erase from cursor to end of display
3098 this.clearRegion(this.cursorX, this.cursorY,
3099 this.terminalWidth - this.cursorX, 1,
3100 this.color, this.style);
3101 if (this.cursorY < this.terminalHeight-2) {
3102 this.clearRegion(0, this.cursorY+1,
3103 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3104 this.color, this.style);
3107 case 1: // Erase from start to cursor
3108 if (this.cursorY > 0) {
3109 this.clearRegion(0, 0,
3110 this.terminalWidth, this.cursorY,
3111 this.color, this.style);
3113 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3114 this.color, this.style);
3116 case 2: // Erase whole display
3117 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3118 this.color, this.style);
3126 VT100.prototype.csiK = function(number) {
3128 case 0: // Erase from cursor to end of line
3129 this.clearRegion(this.cursorX, this.cursorY,
3130 this.terminalWidth - this.cursorX, 1,
3131 this.color, this.style);
3133 case 1: // Erase from start of line to cursor
3134 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3135 this.color, this.style);
3137 case 2: // Erase whole line
3138 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3139 this.color, this.style);
3147 VT100.prototype.csiL = function(number) {
3148 // Open line by inserting blank line(s)
3149 if (this.cursorY >= this.bottom) {
3155 if (number > this.bottom - this.cursorY) {
3156 number = this.bottom - this.cursorY;
3158 this.scrollRegion(0, this.cursorY,
3159 this.terminalWidth, this.bottom - this.cursorY - number,
3160 0, number, this.color, this.style);
3164 VT100.prototype.csiM = function(number) {
3165 // Delete line(s), scrolling up the bottom of the screen.
3166 if (this.cursorY >= this.bottom) {
3172 if (number > this.bottom - this.cursorY) {
3173 number = bottom - cursorY;
3175 this.scrollRegion(0, this.cursorY + number,
3176 this.terminalWidth, this.bottom - this.cursorY - number,
3177 0, -number, this.color, this.style);
3181 VT100.prototype.csim = function() {
3182 for (var i = 0; i <= this.npar; i++) {
3183 switch (this.par[i]) {
3184 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
3185 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
3186 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
3187 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
3188 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
3189 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
3191 this.translate = this.GMap[this.useGMap];
3192 this.dispCtrl = false;
3193 this.toggleMeta = false;
3196 this.translate = this.CodePage437Map;
3197 this.dispCtrl = true;
3198 this.toggleMeta = false;
3201 this.translate = this.CodePage437Map;
3202 this.dispCtrl = true;
3203 this.toggleMeta = true;
3206 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
3207 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
3208 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
3209 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
3210 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
3211 0x0200 /* ATTR_UNDERLINE */; break;
3212 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
3213 case 49: this.attr |= 0xF0; break;
3215 if (this.par[i] >= 30 && this.par[i] <= 37) {
3216 var fg = this.par[i] - 30;
3217 this.attr = (this.attr & ~0x0F) | fg;
3218 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3219 var bg = this.par[i] - 40;
3220 this.attr = (this.attr & ~0xF0) | (bg << 4);
3228 VT100.prototype.csiP = function(number) {
3229 // Delete character(s) following cursor
3233 if (number > this.terminalWidth - this.cursorX) {
3234 number = this.terminalWidth - this.cursorX;
3236 this.scrollRegion(this.cursorX + number, this.cursorY,
3237 this.terminalWidth - this.cursorX - number, 1,
3238 -number, 0, this.color, this.style);
3242 VT100.prototype.csiX = function(number) {
3243 // Clear characters following cursor
3247 if (number > this.terminalWidth - this.cursorX) {
3248 number = this.terminalWidth - this.cursorX;
3250 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3251 this.color, this.style);
3255 VT100.prototype.settermCommand = function() {
3256 // Setterm commands are not implemented
3259 VT100.prototype.doControl = function(ch) {
3260 if (this.printing) {
3261 this.sendControlToPrinter(ch);
3266 case 0x00: /* ignored */ break;
3267 case 0x08: this.bs(); break;
3268 case 0x09: this.ht(); break;
3272 case 0x84: this.lf(); if (!this.crLfMode) break;
3273 case 0x0D: this.cr(); break;
3274 case 0x85: this.cr(); this.lf(); break;
3275 case 0x0E: this.useGMap = 1;
3276 this.translate = this.GMap[1];
3277 this.dispCtrl = true; break;
3278 case 0x0F: this.useGMap = 0;
3279 this.translate = this.GMap[0];
3280 this.dispCtrl = false; break;
3282 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3283 case 0x1B: this.isEsc = 1 /* ESesc */; break;
3284 case 0x7F: /* ignored */ break;
3285 case 0x88: this.userTabStop[this.cursorX] = true; break;
3286 case 0x8D: this.ri(); break;
3287 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3288 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3289 case 0x9A: this.respondID(); break;
3290 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3291 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
3295 default: switch (this.isEsc) {
3297 this.isEsc = 0 /* ESnormal */;
3299 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3300 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3302 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3304 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3306 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3307 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3308 /*7*/ case 0x37: this.saveCursor(); break;
3309 /*8*/ case 0x38: this.restoreCursor(); break;
3310 /*>*/ case 0x3E: this.applKeyMode = false; break;
3311 /*=*/ case 0x3D: this.applKeyMode = true; break;
3312 /*D*/ case 0x44: this.lf(); break;
3313 /*E*/ case 0x45: this.cr(); this.lf(); break;
3314 /*M*/ case 0x4D: this.ri(); break;
3315 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3316 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3317 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3318 /*Z*/ case 0x5A: this.respondID(); break;
3319 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3320 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3321 /*c*/ case 0x63: this.reset(); break;
3322 /*g*/ case 0x67: this.flashScreen(); break;
3326 case 15 /* ESnonstd */:
3330 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3331 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3332 this.isEsc = 16 /* ESpalette */; break;
3333 /*R*/ case 0x52: // Palette support is not implemented
3334 this.isEsc = 0 /* ESnormal */; break;
3335 default: this.isEsc = 0 /* ESnormal */; break;
3338 case 16 /* ESpalette */:
3339 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3340 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3341 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3342 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3344 if (this.npar == 7) {
3345 // Palette support is not implemented
3346 this.isEsc = 0 /* ESnormal */;
3349 this.isEsc = 0 /* ESnormal */;
3352 case 2 /* ESsquare */:
3354 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3355 0, 0, 0, 0, 0, 0, 0, 0 ];
3356 this.isEsc = 3 /* ESgetpars */;
3357 /*[*/ if (ch == 0x5B) { // Function key
3358 this.isEsc = 6 /* ESfunckey */;
3361 /*?*/ this.isQuestionMark = ch == 0x3F;
3362 if (this.isQuestionMark) {
3367 case 5 /* ESdeviceattr */:
3368 case 3 /* ESgetpars */:
3369 /*;*/ if (ch == 0x3B) {
3372 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3373 var par = this.par[this.npar];
3374 if (par == undefined) {
3377 this.par[this.npar] = 10*par + (ch & 0xF);
3379 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3381 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3382 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3383 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3384 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3387 this.isEsc = 0 /* ESnormal */;
3390 this.isEsc = 4 /* ESgotpars */;
3393 case 4 /* ESgotpars */:
3394 this.isEsc = 0 /* ESnormal */;
3395 if (this.isQuestionMark) {
3397 /*h*/ case 0x68: this.setMode(true); break;
3398 /*l*/ case 0x6C: this.setMode(false); break;
3399 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3402 this.isQuestionMark = false;
3406 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3407 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3409 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3410 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3411 this.cursorY - (this.par[0] ? this.par[0] : 1));
3414 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3415 this.cursorY + (this.par[0] ? this.par[0] : 1));
3418 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3419 this.cursorY); break;
3420 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3421 this.cursorY); break;
3422 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3424 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3426 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3428 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3429 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3430 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3431 /*i*/ case 0x69: this.csii(this.par[0]); break;
3432 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3433 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3434 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3435 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3436 /*m*/ case 0x6D: this.csim(); break;
3437 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3438 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3439 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3440 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3441 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3442 /*g*/ case 0x67: if (this.par[0] == 0) {
3443 this.userTabStop[this.cursorX] = false;
3444 } else if (this.par[0] == 2 || this.par[0] == 3) {
3445 this.userTabStop = [ ];
3446 for (var i = 0; i < this.terminalWidth; i++) {
3447 this.userTabStop[i] = false;
3451 /*h*/ case 0x68: this.setMode(true); break;
3452 /*l*/ case 0x6C: this.setMode(false); break;
3453 /*n*/ case 0x6E: switch (this.par[0]) {
3454 case 5: this.statusReport(); break;
3455 case 6: this.cursorReport(); break;
3459 /*q*/ case 0x71: // LED control not implemented
3461 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3462 var b = this.par[1] ? this.par[1]
3463 : this.terminalHeight;
3464 if (t < b && b <= this.terminalHeight) {
3470 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3471 if (c > this.terminalWidth * this.terminalHeight) {
3472 c = this.terminalWidth * this.terminalHeight;
3475 lineBuf += this.lastCharacter;
3478 /*s*/ case 0x73: this.saveCursor(); break;
3479 /*u*/ case 0x75: this.restoreCursor(); break;
3480 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3481 /*]*/ case 0x5D: this.settermCommand(); break;
3485 case 12 /* ESbang */:
3489 this.isEsc = 0 /* ESnormal */;
3491 case 13 /* ESpercent */:
3492 this.isEsc = 0 /* ESnormal */;
3494 /*@*/ case 0x40: this.utfEnabled = false; break;
3496 /*8*/ case 0x38: this.utfEnabled = true; break;
3500 case 6 /* ESfunckey */:
3501 this.isEsc = 0 /* ESnormal */; break;
3502 case 7 /* EShash */:
3503 this.isEsc = 0 /* ESnormal */;
3504 /*8*/ if (ch == 0x38) {
3505 // Screen alignment test not implemented
3508 case 8 /* ESsetG0 */:
3509 case 9 /* ESsetG1 */:
3510 case 10 /* ESsetG2 */:
3511 case 11 /* ESsetG3 */:
3512 var g = this.isEsc - 8 /* ESsetG0 */;
3513 this.isEsc = 0 /* ESnormal */;
3515 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3517 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3518 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3519 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3522 if (this.useGMap == g) {
3523 this.translate = this.GMap[g];
3526 case 17 /* ESstatus */:
3528 if (this.statusString && this.statusString.charAt(0) == ';') {
3529 this.statusString = this.statusString.substr(1);
3532 window.status = this.statusString;
3535 this.isEsc = 0 /* ESnormal */;
3537 this.statusString += String.fromCharCode(ch);
3540 case 18 /* ESss2 */:
3541 case 19 /* ESss3 */:
3543 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3544 [this.toggleMeta ? (ch | 0x80) : ch];
3545 if ((ch & 0xFF00) == 0xF000) {
3547 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3548 this.isEsc = 0 /* ESnormal */; break;
3551 this.lastCharacter = String.fromCharCode(ch);
3552 lineBuf += this.lastCharacter;
3553 this.isEsc = 0 /* ESnormal */; break;
3555 this.isEsc = 0 /* ESnormal */; break;
3562 VT100.prototype.renderString = function(s, showCursor) {
3563 if (this.printing) {
3564 this.sendToPrinter(s);
3571 // We try to minimize the number of DOM operations by coalescing individual
3572 // characters into strings. This is a significant performance improvement.
3573 var incX = s.length;
3574 if (incX > this.terminalWidth - this.cursorX) {
3575 incX = this.terminalWidth - this.cursorX;
3579 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3582 // Minimize the number of calls to putString(), by avoiding a direct
3583 // call to this.showCursor()
3584 this.cursor.style.visibility = '';
3586 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3589 VT100.prototype.vt100 = function(s) {
3590 this.cursorNeedsShowing = this.hideCursor();
3591 this.respondString = '';
3593 for (var i = 0; i < s.length; i++) {
3594 var ch = s.charCodeAt(i);
3595 if (this.utfEnabled) {
3596 // Decode UTF8 encoded character
3598 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3599 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3600 if (--this.utfCount <= 0) {
3601 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3610 if ((ch & 0xE0) == 0xC0) {
3612 this.utfChar = ch & 0x1F;
3613 } else if ((ch & 0xF0) == 0xE0) {
3615 this.utfChar = ch & 0x0F;
3616 } else if ((ch & 0xF8) == 0xF0) {
3618 this.utfChar = ch & 0x07;
3619 } else if ((ch & 0xFC) == 0xF8) {
3621 this.utfChar = ch & 0x03;
3622 } else if ((ch & 0xFE) == 0xFC) {
3624 this.utfChar = ch & 0x01;
3634 var isNormalCharacter =
3635 (ch >= 32 && ch <= 127 || ch >= 160 ||
3636 this.utfEnabled && ch >= 128 ||
3637 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3638 (ch != 0x7F || this.dispCtrl);
3640 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3642 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3644 if ((ch & 0xFF00) == 0xF000) {
3646 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3649 if (!this.printing) {
3650 if (this.needWrap || this.insertMode) {
3652 this.renderString(lineBuf);
3656 if (this.needWrap) {
3657 this.cr(); this.lf();
3659 if (this.insertMode) {
3660 this.scrollRegion(this.cursorX, this.cursorY,
3661 this.terminalWidth - this.cursorX - 1, 1,
3662 1, 0, this.color, this.style);
3665 this.lastCharacter = String.fromCharCode(ch);
3666 lineBuf += this.lastCharacter;
3667 if (!this.printing &&
3668 this.cursorX + lineBuf.length >= this.terminalWidth) {
3669 this.needWrap = this.autoWrapMode;
3673 this.renderString(lineBuf);
3676 var expand = this.doControl(ch);
3677 if (expand.length) {
3678 var r = this.respondString;
3679 this.respondString= r + this.vt100(expand);
3684 this.renderString(lineBuf, this.cursorNeedsShowing);
3685 } else if (this.cursorNeedsShowing) {
3688 return this.respondString;
3691 VT100.prototype.Latin1Map = [
3692 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3693 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3694 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3695 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3696 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3697 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3698 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3699 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3700 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3701 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3702 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3703 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3704 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3705 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3706 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3707 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3708 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3709 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3710 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3711 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3712 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3713 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3714 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3715 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3716 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3717 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3718 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3719 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3720 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3721 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3722 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3723 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3726 VT100.prototype.VT100GraphicsMap = [
3727 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3728 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3729 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3730 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3731 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3732 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3733 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3734 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3735 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3736 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3737 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3738 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3739 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3740 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3741 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3742 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3743 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3744 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3745 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3746 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3747 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3748 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3749 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3750 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3751 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3752 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3753 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3754 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3755 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3756 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3757 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3758 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3761 VT100.prototype.CodePage437Map = [
3762 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3763 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3764 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3765 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3766 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3767 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3768 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3769 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3770 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3771 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3772 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3773 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3774 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3775 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3776 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3777 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3778 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3779 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3780 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3781 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3782 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3783 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3784 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3785 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3786 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3787 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3788 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3789 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3790 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3791 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3792 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3793 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3796 VT100.prototype.DirectToFontMap = [
3797 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3798 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3799 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3800 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3801 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3802 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3803 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3804 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3805 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3806 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3807 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3808 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3809 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3810 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3811 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3812 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3813 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3814 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3815 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3816 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3817 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3818 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3819 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3820 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3821 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3822 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3823 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3824 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3825 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3826 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3827 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3828 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3831 VT100.prototype.ctrlAction = [
3832 true, false, false, false, false, false, false, true,
3833 true, true, true, true, true, true, true, true,
3834 false, false, false, false, false, false, false, false,
3835 true, false, true, true, false, false, false, false
3838 VT100.prototype.ctrlAlways = [
3839 true, false, false, false, false, false, false, false,
3840 true, false, true, false, true, true, true, true,
3841 false, false, false, false, false, false, false, false,
3842 false, false, false, true, false, false, false, false