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 176)" +
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.handleKey = function(event) {
2060 if (typeof event.charCode != 'undefined') {
2061 // non-IE keypress events have a translated charCode value. Also, our
2062 // fake events generated when receiving keydown events include this data
2064 ch = event.charCode;
2065 key = event.keyCode;
2067 // When sending a keypress event, IE includes the translated character
2068 // code in the keyCode field.
2073 // Apply modifier keys (ctrl and shift)
2076 if (event.ctrlKey) {
2077 if (ch >= 32 && ch <= 127) {
2081 if (event.shiftKey) {
2082 if (ch >= 97 && ch <= 122) {
2086 if (ch >= 65 && ch <= 90) {
2095 // By this point, "ch" is either defined and contains the character code, or
2096 // it is undefined and "key" defines the code of a function key
2097 if (ch != undefined) {
2098 ch = String.fromCharCode(ch);
2099 this.scrollable.scrollTop = this.numScrollbackLines *
2100 this.cursorHeight + 1;
2102 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2103 // Many programs have difficulties dealing with parametrized escape
2104 // sequences for function keys. Thus, if ALT is the only modifier
2105 // key, return Emacs-style keycodes for commonly used keys.
2107 case 33: /* Page Up */ ch = '\u001B<'; break;
2108 case 34: /* Page Down */ ch = '\u001B>'; break;
2109 case 37: /* Left */ ch = '\u001Bb'; break;
2110 case 38: /* Up */ ch = '\u001Bp'; break;
2111 case 39: /* Right */ ch = '\u001Bf'; break;
2112 case 40: /* Down */ ch = '\u001Bn'; break;
2113 case 46: /* Delete */ ch = '\u001Bd'; break;
2116 } else if (event.shiftKey && !event.ctrlKey &&
2117 !event.altKey && !event.metaKey) {
2119 case 33: /* Page Up */ this.scrollBack(); return;
2120 case 34: /* Page Down */ this.scrollFore(); return;
2124 if (ch == undefined) {
2126 case 8: /* Backspace */ ch = '\u007f'; break;
2127 case 9: /* Tab */ ch = '\u0009'; break;
2128 case 10: /* Return */ ch = '\u000A'; break;
2129 case 13: /* Enter */ ch = this.crLfMode ?
2130 '\r\n' : '\r'; break;
2131 case 16: /* Shift */ return;
2132 case 17: /* Ctrl */ return;
2133 case 18: /* Alt */ return;
2134 case 19: /* Break */ return;
2135 case 20: /* Caps Lock */ return;
2136 case 27: /* Escape */ ch = '\u001B'; break;
2137 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2138 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2139 case 35: /* End */ ch = '\u001BOF'; break;
2140 case 36: /* Home */ ch = '\u001BOH'; break;
2141 case 37: /* Left */ ch = this.cursorKeyMode ?
2142 '\u001BOD' : '\u001B[D'; break;
2143 case 38: /* Up */ ch = this.cursorKeyMode ?
2144 '\u001BOA' : '\u001B[A'; break;
2145 case 39: /* Right */ ch = this.cursorKeyMode ?
2146 '\u001BOC' : '\u001B[C'; break;
2147 case 40: /* Down */ ch = this.cursorKeyMode ?
2148 '\u001BOB' : '\u001B[B'; break;
2149 case 45: /* Insert */ ch = '\u001B[2~'; break;
2150 case 46: /* Delete */ ch = '\u001B[3~'; break;
2151 case 91: /* Left Window */ return;
2152 case 92: /* Right Window */ return;
2153 case 93: /* Select */ return;
2154 case 96: /* 0 */ ch = '0'; break;
2155 case 97: /* 1 */ ch = '1'; break;
2156 case 98: /* 2 */ ch = '2'; break;
2157 case 99: /* 3 */ ch = '3'; break;
2158 case 100: /* 4 */ ch = '4'; break;
2159 case 101: /* 5 */ ch = '5'; break;
2160 case 102: /* 6 */ ch = '6'; break;
2161 case 103: /* 7 */ ch = '7'; break;
2162 case 104: /* 8 */ ch = '8'; break;
2163 case 105: /* 9 */ ch = '9'; break;
2164 case 106: /* * */ ch = '*'; break;
2165 case 107: /* + */ ch = '+'; break;
2166 case 109: /* - */ ch = '-'; break;
2167 case 110: /* . */ ch = '.'; break;
2168 case 111: /* / */ ch = '/'; break;
2169 case 112: /* F1 */ ch = '\u001BOP'; break;
2170 case 113: /* F2 */ ch = '\u001BOQ'; break;
2171 case 114: /* F3 */ ch = '\u001BOR'; break;
2172 case 115: /* F4 */ ch = '\u001BOS'; break;
2173 case 116: /* F5 */ ch = '\u001B[15~'; break;
2174 case 117: /* F6 */ ch = '\u001B[17~'; break;
2175 case 118: /* F7 */ ch = '\u001B[18~'; break;
2176 case 119: /* F8 */ ch = '\u001B[19~'; break;
2177 case 120: /* F9 */ ch = '\u001B[20~'; break;
2178 case 121: /* F10 */ ch = '\u001B[21~'; break;
2179 case 122: /* F11 */ ch = '\u001B[23~'; break;
2180 case 123: /* F12 */ ch = '\u001B[24~'; break;
2181 case 144: /* Num Lock */ return;
2182 case 145: /* Scroll Lock */ return;
2185 this.scrollable.scrollTop = this.numScrollbackLines *
2186 this.cursorHeight + 1;
2190 // "ch" now contains the sequence of keycodes to send. But we might still
2191 // have to apply the effects of modifier keys.
2192 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2193 var start, digit, part1, part2;
2194 if ((start = ch.substr(0, 2)) == '\u001B[') {
2196 part1.length < ch.length &&
2197 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2198 part1 = ch.substr(0, part1.length + 1);
2200 part2 = ch.substr(part1.length);
2201 if (part1.length > 2) {
2204 } else if (start == '\u001BO') {
2206 part2 = ch.substr(2);
2208 if (part1 != undefined) {
2210 ((event.shiftKey ? 1 : 0) +
2211 (event.altKey|event.metaKey ? 2 : 0) +
2212 (event.ctrlKey ? 4 : 0)) +
2214 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2219 if (this.menu.style.visibility == 'hidden') {
2220 // this.vt100('R: c=');
2221 // for (var i = 0; i < ch.length; i++)
2222 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2223 // this.vt100('\r\n');
2224 this.keysPressed(ch);
2228 VT100.prototype.inspect = function(o, d) {
2229 if (d == undefined) {
2233 if (typeof o == 'object' && ++d < 2) {
2236 rc += this.spaces(d * 2) + i + ' -> ';
2238 rc += this.inspect(o[i], d);
2240 rc += '?' + '?' + '?\r\n';
2245 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2250 VT100.prototype.checkComposedKeys = function(event) {
2251 // Composed keys (at least on Linux) do not generate normal events.
2252 // Instead, they get entered into the text field. We normally catch
2253 // this on the next keyup event.
2254 var s = this.input.value;
2256 this.input.value = '';
2257 if (this.menu.style.visibility == 'hidden') {
2258 this.keysPressed(s);
2263 VT100.prototype.fixEvent = function(event) {
2264 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2265 // is used as a second-level selector, clear the modifier bits before
2266 // handling the event.
2267 if (event.ctrlKey && event.altKey) {
2269 fake.charCode = event.charCode;
2270 fake.keyCode = event.keyCode;
2271 fake.ctrlKey = false;
2272 fake.shiftKey = event.shiftKey;
2273 fake.altKey = false;
2274 fake.metaKey = event.metaKey;
2278 // Some browsers fail to translate keys, if both shift and alt/meta is
2279 // pressed at the same time. We try to translate those cases, but that
2280 // only works for US keyboard layouts.
2281 if (event.shiftKey) {
2284 switch (this.lastNormalKeyDownEvent.keyCode) {
2285 case 39: /* ' -> " */ u = 39; s = 34; break;
2286 case 44: /* , -> < */ u = 44; s = 60; break;
2287 case 45: /* - -> _ */ u = 45; s = 95; break;
2288 case 46: /* . -> > */ u = 46; s = 62; break;
2289 case 47: /* / -> ? */ u = 47; s = 63; break;
2291 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2292 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2293 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2294 case 51: /* 3 -> # */ u = 51; s = 35; break;
2295 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2296 case 53: /* 5 -> % */ u = 53; s = 37; break;
2297 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2298 case 55: /* 7 -> & */ u = 55; s = 38; break;
2299 case 56: /* 8 -> * */ u = 56; s = 42; break;
2300 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2302 case 59: /* ; -> : */ u = 59; s = 58; break;
2303 case 61: /* = -> + */ u = 61; s = 43; break;
2304 case 91: /* [ -> { */ u = 91; s = 123; break;
2305 case 92: /* \ -> | */ u = 92; s = 124; break;
2306 case 93: /* ] -> } */ u = 93; s = 125; break;
2307 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2309 case 109: /* - -> _ */ u = 45; s = 95; break;
2310 case 111: /* / -> ? */ u = 47; s = 63; break;
2312 case 186: /* ; -> : */ u = 59; s = 58; break;
2313 case 187: /* = -> + */ u = 61; s = 43; break;
2314 case 188: /* , -> < */ u = 44; s = 60; break;
2315 case 189: /* - -> _ */ u = 45; s = 95; break;
2316 case 190: /* . -> > */ u = 46; s = 62; break;
2317 case 191: /* / -> ? */ u = 47; s = 63; break;
2318 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2319 case 219: /* [ -> { */ u = 91; s = 123; break;
2320 case 220: /* \ -> | */ u = 92; s = 124; break;
2321 case 221: /* ] -> } */ u = 93; s = 125; break;
2322 case 222: /* ' -> " */ u = 39; s = 34; break;
2325 if (s && (event.charCode == u || event.charCode == 0)) {
2328 fake.keyCode = event.keyCode;
2329 fake.ctrlKey = event.ctrlKey;
2330 fake.shiftKey = event.shiftKey;
2331 fake.altKey = event.altKey;
2332 fake.metaKey = event.metaKey;
2339 VT100.prototype.keyDown = function(event) {
2340 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2341 // (event.shiftKey || event.ctrlKey || event.altKey ||
2342 // event.metaKey ? ', ' +
2343 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2344 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2346 this.checkComposedKeys(event);
2347 this.lastKeyPressedEvent = undefined;
2348 this.lastKeyDownEvent = undefined;
2349 this.lastNormalKeyDownEvent = event;
2352 event.keyCode == 32 ||
2353 event.keyCode >= 48 && event.keyCode <= 57 ||
2354 event.keyCode >= 65 && event.keyCode <= 90;
2357 event.keyCode >= 96 && event.keyCode <= 105 ||
2358 event.keyCode == 226;
2361 event.keyCode == 59 || event.keyCode == 61 ||
2362 event.keyCode == 106 || event.keyCode == 107 ||
2363 event.keyCode >= 109 && event.keyCode <= 111 ||
2364 event.keyCode >= 186 && event.keyCode <= 192 ||
2365 event.keyCode >= 219 && event.keyCode <= 222 ||
2366 event.keyCode == 252;
2368 if (navigator.appName == 'Konqueror') {
2369 normalKey |= event.keyCode < 128;
2374 // We normally prefer to look at keypress events, as they perform the
2375 // translation from keyCode to charCode. This is important, as the
2376 // translation is locale-dependent.
2377 // But for some keys, we must intercept them during the keydown event,
2378 // as they would otherwise get interpreted by the browser.
2379 // Even, when doing all of this, there are some keys that we can never
2380 // intercept. This applies to some of the menu navigation keys in IE.
2381 // In fact, we see them, but we cannot stop IE from seeing them, too.
2382 if ((event.charCode || event.keyCode) &&
2383 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2385 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2386 // interpret this sequence ourselves, as some keyboard layouts use
2387 // it for second-level layouts.
2388 !(event.ctrlKey && event.altKey)) ||
2389 this.catchModifiersEarly && normalKey && !alphNumKey &&
2390 (event.ctrlKey || event.altKey || event.metaKey) ||
2392 this.lastKeyDownEvent = event;
2394 fake.ctrlKey = event.ctrlKey;
2395 fake.shiftKey = event.shiftKey;
2396 fake.altKey = event.altKey;
2397 fake.metaKey = event.metaKey;
2399 fake.charCode = event.keyCode;
2403 fake.keyCode = event.keyCode;
2404 if (!alphNumKey && event.shiftKey) {
2405 fake = this.fixEvent(fake);
2409 this.handleKey(fake);
2410 this.lastNormalKeyDownEvent = undefined;
2413 // For non-IE browsers
2414 event.stopPropagation();
2415 event.preventDefault();
2420 event.cancelBubble = true;
2421 event.returnValue = false;
2431 VT100.prototype.keyPressed = function(event) {
2432 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2433 // (event.shiftKey || event.ctrlKey || event.altKey ||
2434 // event.metaKey ? ', ' +
2435 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2436 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2438 if (this.lastKeyDownEvent) {
2439 // If we already processed the key on keydown, do not process it
2440 // again here. Ideally, the browser should not even have generated a
2441 // keypress event in this case. But that does not appear to always work.
2442 this.lastKeyDownEvent = undefined;
2444 this.handleKey(event.altKey || event.metaKey
2445 ? this.fixEvent(event) : event);
2449 // For non-IE browsers
2450 event.preventDefault();
2456 event.cancelBubble = true;
2457 event.returnValue = false;
2462 this.lastNormalKeyDownEvent = undefined;
2463 this.lastKeyPressedEvent = event;
2467 VT100.prototype.keyUp = function(event) {
2468 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2469 // (event.shiftKey || event.ctrlKey || event.altKey ||
2470 // event.metaKey ? ', ' +
2471 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2472 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2474 if (this.lastKeyPressedEvent) {
2475 // The compose key on Linux occasionally confuses the browser and keeps
2476 // inserting bogus characters into the input field, even if just a regular
2477 // key has been pressed. Detect this case and drop the bogus characters.
2479 event.srcElement).value = '';
2481 // This is usually were we notice that a key has been composed and
2482 // thus failed to generate normal events.
2483 this.checkComposedKeys(event);
2485 // Some browsers don't report keypress events if ctrl or alt is pressed
2486 // for non-alphanumerical keys. Patch things up for now, but in the
2487 // future we will catch these keys earlier (in the keydown handler).
2488 if (this.lastNormalKeyDownEvent) {
2489 this.catchModifiersEarly = true;
2491 event.keyCode == 32 ||
2492 event.keyCode >= 48 && event.keyCode <= 57 ||
2493 event.keyCode >= 65 && event.keyCode <= 90;
2496 event.keyCode >= 96 && event.keyCode <= 105;
2499 event.keyCode == 59 || event.keyCode == 61 ||
2500 event.keyCode == 106 || event.keyCode == 107 ||
2501 event.keyCode >= 109 && event.keyCode <= 111 ||
2502 event.keyCode >= 186 && event.keyCode <= 192 ||
2503 event.keyCode >= 219 && event.keyCode <= 222 ||
2504 event.keyCode == 252;
2506 fake.ctrlKey = event.ctrlKey;
2507 fake.shiftKey = event.shiftKey;
2508 fake.altKey = event.altKey;
2509 fake.metaKey = event.metaKey;
2511 fake.charCode = event.keyCode;
2515 fake.keyCode = event.keyCode;
2516 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2517 fake = this.fixEvent(fake);
2520 this.lastNormalKeyDownEvent = undefined;
2521 this.handleKey(fake);
2527 event.cancelBubble = true;
2528 event.returnValue = false;
2533 this.lastKeyDownEvent = undefined;
2534 this.lastKeyPressedEvent = undefined;
2538 VT100.prototype.animateCursor = function(inactive) {
2539 if (!this.cursorInterval) {
2540 this.cursorInterval = setInterval(
2543 vt100.animateCursor();
2545 // Use this opportunity to check whether the user entered a composed
2546 // key, or whether somebody pasted text into the textfield.
2547 vt100.checkComposedKeys();
2551 if (inactive != undefined || this.cursor.className != 'inactive') {
2553 this.cursor.className = 'inactive';
2555 this.cursor.className = this.cursor.className == 'bright'
2561 VT100.prototype.blurCursor = function() {
2562 this.animateCursor(true);
2565 VT100.prototype.focusCursor = function() {
2566 this.animateCursor(false);
2569 VT100.prototype.flashScreen = function() {
2570 this.isInverted = !this.isInverted;
2571 this.refreshInvertedState();
2572 this.isInverted = !this.isInverted;
2573 setTimeout(function(vt100) {
2575 vt100.refreshInvertedState();
2580 VT100.prototype.beep = function() {
2581 if (this.visualBell) {
2588 this.beeper.src = 'beep.wav';
2595 VT100.prototype.bs = function() {
2596 if (this.cursorX > 0) {
2597 this.gotoXY(this.cursorX - 1, this.cursorY);
2598 this.needWrap = false;
2602 VT100.prototype.ht = function(count) {
2603 if (count == undefined) {
2606 var cx = this.cursorX;
2607 while (count-- > 0) {
2608 while (cx++ < this.terminalWidth) {
2609 var tabState = this.userTabStop[cx];
2610 if (tabState == false) {
2611 // Explicitly cleared tab stop
2613 } else if (tabState) {
2614 // Explicitly set tab stop
2617 // Default tab stop at each eighth column
2624 if (cx > this.terminalWidth - 1) {
2625 cx = this.terminalWidth - 1;
2627 if (cx != this.cursorX) {
2628 this.gotoXY(cx, this.cursorY);
2632 VT100.prototype.rt = function(count) {
2633 if (count == undefined) {
2636 var cx = this.cursorX;
2637 while (count-- > 0) {
2639 var tabState = this.userTabStop[cx];
2640 if (tabState == false) {
2641 // Explicitly cleared tab stop
2643 } else if (tabState) {
2644 // Explicitly set tab stop
2647 // Default tab stop at each eighth column
2657 if (cx != this.cursorX) {
2658 this.gotoXY(cx, this.cursorY);
2662 VT100.prototype.cr = function() {
2663 this.gotoXY(0, this.cursorY);
2664 this.needWrap = false;
2667 VT100.prototype.lf = function(count) {
2668 if (count == undefined) {
2671 if (count > this.terminalHeight) {
2672 count = this.terminalHeight;
2678 while (count-- > 0) {
2679 if (this.cursorY == this.bottom - 1) {
2680 this.scrollRegion(0, this.top + 1,
2681 this.terminalWidth, this.bottom - this.top - 1,
2682 0, -1, this.color, this.style);
2684 } else if (this.cursorY < this.terminalHeight - 1) {
2685 this.gotoXY(this.cursorX, this.cursorY + 1);
2690 VT100.prototype.ri = function(count) {
2691 if (count == undefined) {
2694 if (count > this.terminalHeight) {
2695 count = this.terminalHeight;
2701 while (count-- > 0) {
2702 if (this.cursorY == this.top) {
2703 this.scrollRegion(0, this.top,
2704 this.terminalWidth, this.bottom - this.top - 1,
2705 0, 1, this.color, this.style);
2706 } else if (this.cursorY > 0) {
2707 this.gotoXY(this.cursorX, this.cursorY - 1);
2710 this.needWrap = false;
2713 VT100.prototype.respondID = function() {
2714 this.respondString += '\u001B[?6c';
2717 VT100.prototype.respondSecondaryDA = function() {
2718 this.respondString += '\u001B[>0;0;0c';
2722 VT100.prototype.updateStyle = function() {
2724 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2725 this.style = 'text-decoration:underline;';
2727 var bg = (this.attr >> 4) & 0xF;
2728 var fg = this.attr & 0xF;
2729 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2734 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2735 fg = 8; // Dark grey
2736 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2739 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2742 // Make some readability enhancements. Most notably, disallow identical
2743 // background and foreground colors.
2745 if ((fg ^= 8) == 7) {
2749 // And disallow bright colors on a light-grey background.
2750 if (bg == 7 && fg >= 8) {
2751 if ((fg -= 8) == 7) {
2756 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2759 VT100.prototype.setAttrColors = function(attr) {
2760 if (attr != this.attr) {
2766 VT100.prototype.saveCursor = function() {
2767 this.savedX[this.currentScreen] = this.cursorX;
2768 this.savedY[this.currentScreen] = this.cursorY;
2769 this.savedAttr[this.currentScreen] = this.attr;
2770 this.savedUseGMap = this.useGMap;
2771 for (var i = 0; i < 4; i++) {
2772 this.savedGMap[i] = this.GMap[i];
2774 this.savedValid[this.currentScreen] = true;
2777 VT100.prototype.restoreCursor = function() {
2778 if (!this.savedValid[this.currentScreen]) {
2781 this.attr = this.savedAttr[this.currentScreen];
2783 this.useGMap = this.savedUseGMap;
2784 for (var i = 0; i < 4; i++) {
2785 this.GMap[i] = this.savedGMap[i];
2787 this.translate = this.GMap[this.useGMap];
2788 this.needWrap = false;
2789 this.gotoXY(this.savedX[this.currentScreen],
2790 this.savedY[this.currentScreen]);
2793 VT100.prototype.setMode = function(state) {
2794 for (var i = 0; i <= this.npar; i++) {
2795 if (this.isQuestionMark) {
2796 switch (this.par[i]) {
2797 case 1: this.cursorKeyMode = state; break;
2798 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2799 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2800 case 6: this.offsetMode = state; break;
2801 case 7: this.autoWrapMode = state; break;
2803 case 9: this.mouseReporting = state; break;
2804 case 25: this.cursorNeedsShowing = state;
2805 if (state) { this.showCursor(); }
2806 else { this.hideCursor(); } break;
2809 case 47: this.enableAlternateScreen(state); break;
2813 switch (this.par[i]) {
2814 case 3: this.dispCtrl = state; break;
2815 case 4: this.insertMode = state; break;
2816 case 20:this.crLfMode = state; break;
2823 VT100.prototype.statusReport = function() {
2824 // Ready and operational.
2825 this.respondString += '\u001B[0n';
2828 VT100.prototype.cursorReport = function() {
2829 this.respondString += '\u001B[' +
2830 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2832 (this.cursorX + 1) +
2836 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2837 // Changing of cursor color is not implemented.
2840 VT100.prototype.openPrinterWindow = function() {
2843 if (!this.printWin || this.printWin.closed) {
2844 this.printWin = window.open('', 'print-output',
2845 'width=800,height=600,directories=no,location=no,menubar=yes,' +
2846 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
2847 this.printWin.document.body.innerHTML =
2848 '<link rel="stylesheet" href="' +
2849 document.location.protocol + '//' + document.location.host +
2850 document.location.pathname.replace(/[^/]*$/, '') +
2851 'print-styles.css" type="text/css">\n' +
2852 '<div id="options"><input id="autoprint" type="checkbox"' +
2853 (this.autoprint ? ' checked' : '') + '>' +
2854 'Automatically, print page(s) when job is ready' +
2855 '</input></div>\n' +
2856 '<pre id="print"></pre>\n';
2857 var autoprint = this.printWin.document.getElementById('autoprint');
2858 this.addListener(autoprint, 'click',
2859 (function(vt100, autoprint) {
2861 vt100.autoprint = autoprint.checked;
2862 vt100.storeUserSettings();
2865 })(this, autoprint));
2866 this.printWin.document.title = 'ShellInABox Printer Output';
2869 // Maybe, a popup blocker prevented us from working. Better catch the
2870 // exception, so that we won't break the entire terminal session. The
2871 // user probably needs to disable the blocker first before retrying the
2875 rc &= this.printWin && !this.printWin.closed &&
2876 (this.printWin.innerWidth ||
2877 this.printWin.document.documentElement.clientWidth ||
2878 this.printWin.document.body.clientWidth) > 1;
2880 if (!rc && this.printing == 100) {
2881 // Different popup blockers work differently. We try to detect a couple
2882 // of common methods. And then we retry again a brief amount later, as
2883 // false positives are otherwise possible. If we are sure that there is
2884 // a popup blocker in effect, we alert the user to it. This is helpful
2885 // as some popup blockers have minimal or no UI, and the user might not
2886 // notice that they are missing the popup. In any case, we only show at
2887 // most one message per print job.
2888 this.printing = true;
2889 setTimeout((function(win) {
2891 if (!win || win.closed ||
2893 win.document.documentElement.clientWidth ||
2894 win.document.body.clientWidth) <= 1) {
2895 alert('Attempted to print, but a popup blocker ' +
2896 'prevented the printer window from opening');
2899 })(this.printWin), 2000);
2904 VT100.prototype.sendToPrinter = function(s) {
2905 this.openPrinterWindow();
2907 var doc = this.printWin.document;
2908 var print = doc.getElementById('print');
2909 if (print.lastChild && print.lastChild.nodeName == '#text') {
2910 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
2912 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
2915 // There probably was a more aggressive popup blocker that prevented us
2916 // from accessing the printer windows.
2920 VT100.prototype.sendControlToPrinter = function(ch) {
2921 // We get called whenever doControl() is active. But for the printer, we
2922 // only implement a basic line printer that doesn't understand most of
2923 // the escape sequences of the VT100 terminal. In fact, the only escape
2924 // sequence that we really need to recognize is '^[[5i' for turning the
2930 this.openPrinterWindow();
2931 var doc = this.printWin.document;
2932 var print = doc.getElementById('print');
2933 var chars = print.lastChild &&
2934 print.lastChild.nodeName == '#text' ?
2935 print.lastChild.textContent.length : 0;
2936 this.sendToPrinter(this.spaces(8 - (chars % 8)));
2943 this.openPrinterWindow();
2944 var pageBreak = this.printWin.document.createElement('div');
2945 pageBreak.className = 'pagebreak';
2946 pageBreak.innerHTML = '<hr />';
2947 this.printWin.document.getElementById('print').appendChild(pageBreak);
2951 this.openPrinterWindow();
2952 var lineBreak = this.printWin.document.createElement('br');
2953 this.printWin.document.getElementById('print').appendChild(lineBreak);
2957 this.isEsc = 1 /* ESesc */;
2960 switch (this.isEsc) {
2962 this.isEsc = 0 /* ESnormal */;
2965 this.isEsc = 2 /* ESsquare */;
2971 case 2 /* ESsquare */:
2973 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
2974 0, 0, 0, 0, 0, 0, 0, 0 ];
2975 this.isEsc = 3 /* ESgetpars */;
2976 this.isQuestionMark = ch == 0x3F /*?*/;
2977 if (this.isQuestionMark) {
2981 case 3 /* ESgetpars */:
2982 if (ch == 0x3B /*;*/) {
2985 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
2986 var par = this.par[this.npar];
2987 if (par == undefined) {
2990 this.par[this.npar] = 10*par + (ch & 0xF);
2993 this.isEsc = 4 /* ESgotpars */;
2996 case 4 /* ESgotpars */:
2997 this.isEsc = 0 /* ESnormal */;
2998 if (this.isQuestionMark) {
3003 this.csii(this.par[0]);
3010 this.isEsc = 0 /* ESnormal */;
3016 // There probably was a more aggressive popup blocker that prevented us
3017 // from accessing the printer windows.
3021 VT100.prototype.csiAt = function(number) {
3026 if (number > this.terminalWidth - this.cursorX) {
3027 number = this.terminalWidth - this.cursorX;
3029 this.scrollRegion(this.cursorX, this.cursorY,
3030 this.terminalWidth - this.cursorX - number, 1,
3031 number, 0, this.color, this.style);
3032 this.needWrap = false;
3035 VT100.prototype.csii = function(number) {
3038 case 0: // Print Screen
3041 case 4: // Start printing
3042 if (!this.printing && this.printWin && !this.printWin.closed) {
3043 this.printWin.document.getElementById('print').innerHTML = '';
3045 this.printing = 100;
3047 case 5: // Stop printing
3049 if (this.printing && this.printWin && !this.printWin.closed) {
3050 var print = this.printWin.document.getElementById('print');
3051 while (print.lastChild &&
3052 print.lastChild.tagName == 'DIV' &&
3053 print.lastChild.className == 'pagebreak') {
3054 // Remove trailing blank pages
3055 print.removeChild(print.lastChild);
3057 if (this.autoprint) {
3058 this.printWin.print();
3063 this.printing = false;
3070 VT100.prototype.csiJ = function(number) {
3072 case 0: // Erase from cursor to end of display
3073 this.clearRegion(this.cursorX, this.cursorY,
3074 this.terminalWidth - this.cursorX, 1,
3075 this.color, this.style);
3076 if (this.cursorY < this.terminalHeight-2) {
3077 this.clearRegion(0, this.cursorY+1,
3078 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3079 this.color, this.style);
3082 case 1: // Erase from start to cursor
3083 if (this.cursorY > 0) {
3084 this.clearRegion(0, 0,
3085 this.terminalWidth, this.cursorY,
3086 this.color, this.style);
3088 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3089 this.color, this.style);
3091 case 2: // Erase whole display
3092 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3093 this.color, this.style);
3101 VT100.prototype.csiK = function(number) {
3103 case 0: // Erase from cursor to end of line
3104 this.clearRegion(this.cursorX, this.cursorY,
3105 this.terminalWidth - this.cursorX, 1,
3106 this.color, this.style);
3108 case 1: // Erase from start of line to cursor
3109 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3110 this.color, this.style);
3112 case 2: // Erase whole line
3113 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3114 this.color, this.style);
3122 VT100.prototype.csiL = function(number) {
3123 // Open line by inserting blank line(s)
3124 if (this.cursorY >= this.bottom) {
3130 if (number > this.bottom - this.cursorY) {
3131 number = this.bottom - this.cursorY;
3133 this.scrollRegion(0, this.cursorY,
3134 this.terminalWidth, this.bottom - this.cursorY - number,
3135 0, number, this.color, this.style);
3139 VT100.prototype.csiM = function(number) {
3140 // Delete line(s), scrolling up the bottom of the screen.
3141 if (this.cursorY >= this.bottom) {
3147 if (number > this.bottom - this.cursorY) {
3148 number = bottom - cursorY;
3150 this.scrollRegion(0, this.cursorY + number,
3151 this.terminalWidth, this.bottom - this.cursorY - number,
3152 0, -number, this.color, this.style);
3156 VT100.prototype.csim = function() {
3157 for (var i = 0; i <= this.npar; i++) {
3158 switch (this.par[i]) {
3159 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
3160 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
3161 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
3162 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
3163 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
3164 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
3166 this.translate = this.GMap[this.useGMap];
3167 this.dispCtrl = false;
3168 this.toggleMeta = false;
3171 this.translate = this.CodePage437Map;
3172 this.dispCtrl = true;
3173 this.toggleMeta = false;
3176 this.translate = this.CodePage437Map;
3177 this.dispCtrl = true;
3178 this.toggleMeta = true;
3181 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
3182 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
3183 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
3184 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
3185 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
3186 0x0200 /* ATTR_UNDERLINE */; break;
3187 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
3188 case 49: this.attr |= 0xF0; break;
3190 if (this.par[i] >= 30 && this.par[i] <= 37) {
3191 var fg = this.par[i] - 30;
3192 this.attr = (this.attr & ~0x0F) | fg;
3193 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3194 var bg = this.par[i] - 40;
3195 this.attr = (this.attr & ~0xF0) | (bg << 4);
3203 VT100.prototype.csiP = function(number) {
3204 // Delete character(s) following cursor
3208 if (number > this.terminalWidth - this.cursorX) {
3209 number = this.terminalWidth - this.cursorX;
3211 this.scrollRegion(this.cursorX + number, this.cursorY,
3212 this.terminalWidth - this.cursorX - number, 1,
3213 -number, 0, this.color, this.style);
3217 VT100.prototype.csiX = function(number) {
3218 // Clear characters following cursor
3222 if (number > this.terminalWidth - this.cursorX) {
3223 number = this.terminalWidth - this.cursorX;
3225 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3226 this.color, this.style);
3230 VT100.prototype.settermCommand = function() {
3231 // Setterm commands are not implemented
3234 VT100.prototype.doControl = function(ch) {
3235 if (this.printing) {
3236 this.sendControlToPrinter(ch);
3241 case 0x00: /* ignored */ break;
3242 case 0x08: this.bs(); break;
3243 case 0x09: this.ht(); break;
3247 case 0x84: this.lf(); if (!this.crLfMode) break;
3248 case 0x0D: this.cr(); break;
3249 case 0x85: this.cr(); this.lf(); break;
3250 case 0x0E: this.useGMap = 1;
3251 this.translate = this.GMap[1];
3252 this.dispCtrl = true; break;
3253 case 0x0F: this.useGMap = 0;
3254 this.translate = this.GMap[0];
3255 this.dispCtrl = false; break;
3257 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3258 case 0x1B: this.isEsc = 1 /* ESesc */; break;
3259 case 0x7F: /* ignored */ break;
3260 case 0x88: this.userTabStop[this.cursorX] = true; break;
3261 case 0x8D: this.ri(); break;
3262 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3263 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3264 case 0x9A: this.respondID(); break;
3265 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3266 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
3270 default: switch (this.isEsc) {
3272 this.isEsc = 0 /* ESnormal */;
3274 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3275 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3277 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3279 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3281 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3282 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3283 /*7*/ case 0x37: this.saveCursor(); break;
3284 /*8*/ case 0x38: this.restoreCursor(); break;
3285 /*>*/ case 0x3E: this.applKeyMode = false; break;
3286 /*=*/ case 0x3D: this.applKeyMode = true; break;
3287 /*D*/ case 0x44: this.lf(); break;
3288 /*E*/ case 0x45: this.cr(); this.lf(); break;
3289 /*M*/ case 0x4D: this.ri(); break;
3290 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3291 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3292 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3293 /*Z*/ case 0x5A: this.respondID(); break;
3294 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3295 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3296 /*c*/ case 0x63: this.reset(); break;
3297 /*g*/ case 0x67: this.flashScreen(); break;
3301 case 15 /* ESnonstd */:
3305 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3306 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3307 this.isEsc = 16 /* ESpalette */; break;
3308 /*R*/ case 0x52: // Palette support is not implemented
3309 this.isEsc = 0 /* ESnormal */; break;
3310 default: this.isEsc = 0 /* ESnormal */; break;
3313 case 16 /* ESpalette */:
3314 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3315 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3316 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3317 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3319 if (this.npar == 7) {
3320 // Palette support is not implemented
3321 this.isEsc = 0 /* ESnormal */;
3324 this.isEsc = 0 /* ESnormal */;
3327 case 2 /* ESsquare */:
3329 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3330 0, 0, 0, 0, 0, 0, 0, 0 ];
3331 this.isEsc = 3 /* ESgetpars */;
3332 /*[*/ if (ch == 0x5B) { // Function key
3333 this.isEsc = 6 /* ESfunckey */;
3336 /*?*/ this.isQuestionMark = ch == 0x3F;
3337 if (this.isQuestionMark) {
3342 case 5 /* ESdeviceattr */:
3343 case 3 /* ESgetpars */:
3344 /*;*/ if (ch == 0x3B) {
3347 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3348 var par = this.par[this.npar];
3349 if (par == undefined) {
3352 this.par[this.npar] = 10*par + (ch & 0xF);
3354 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3356 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3357 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3358 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3359 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3362 this.isEsc = 0 /* ESnormal */;
3365 this.isEsc = 4 /* ESgotpars */;
3368 case 4 /* ESgotpars */:
3369 this.isEsc = 0 /* ESnormal */;
3370 if (this.isQuestionMark) {
3372 /*h*/ case 0x68: this.setMode(true); break;
3373 /*l*/ case 0x6C: this.setMode(false); break;
3374 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3377 this.isQuestionMark = false;
3381 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3382 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3384 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3385 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3386 this.cursorY - (this.par[0] ? this.par[0] : 1));
3389 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3390 this.cursorY + (this.par[0] ? this.par[0] : 1));
3393 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3394 this.cursorY); break;
3395 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3396 this.cursorY); break;
3397 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3399 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3401 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3403 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3404 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3405 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3406 /*i*/ case 0x69: this.csii(this.par[0]); break;
3407 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3408 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3409 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3410 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3411 /*m*/ case 0x6D: this.csim(); break;
3412 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3413 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3414 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3415 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3416 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3417 /*g*/ case 0x67: if (this.par[0] == 0) {
3418 this.userTabStop[this.cursorX] = false;
3419 } else if (this.par[0] == 2 || this.par[0] == 3) {
3420 this.userTabStop = [ ];
3421 for (var i = 0; i < this.terminalWidth; i++) {
3422 this.userTabStop[i] = false;
3426 /*h*/ case 0x68: this.setMode(true); break;
3427 /*l*/ case 0x6C: this.setMode(false); break;
3428 /*n*/ case 0x6E: switch (this.par[0]) {
3429 case 5: this.statusReport(); break;
3430 case 6: this.cursorReport(); break;
3434 /*q*/ case 0x71: // LED control not implemented
3436 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3437 var b = this.par[1] ? this.par[1]
3438 : this.terminalHeight;
3439 if (t < b && b <= this.terminalHeight) {
3445 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3446 if (c > this.terminalWidth * this.terminalHeight) {
3447 c = this.terminalWidth * this.terminalHeight;
3450 lineBuf += this.lastCharacter;
3453 /*s*/ case 0x73: this.saveCursor(); break;
3454 /*u*/ case 0x75: this.restoreCursor(); break;
3455 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3456 /*]*/ case 0x5D: this.settermCommand(); break;
3460 case 12 /* ESbang */:
3464 this.isEsc = 0 /* ESnormal */;
3466 case 13 /* ESpercent */:
3467 this.isEsc = 0 /* ESnormal */;
3469 /*@*/ case 0x40: this.utfEnabled = false; break;
3471 /*8*/ case 0x38: this.utfEnabled = true; break;
3475 case 6 /* ESfunckey */:
3476 this.isEsc = 0 /* ESnormal */; break;
3477 case 7 /* EShash */:
3478 this.isEsc = 0 /* ESnormal */;
3479 /*8*/ if (ch == 0x38) {
3480 // Screen alignment test not implemented
3483 case 8 /* ESsetG0 */:
3484 case 9 /* ESsetG1 */:
3485 case 10 /* ESsetG2 */:
3486 case 11 /* ESsetG3 */:
3487 var g = this.isEsc - 8 /* ESsetG0 */;
3488 this.isEsc = 0 /* ESnormal */;
3490 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3492 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3493 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3494 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3497 if (this.useGMap == g) {
3498 this.translate = this.GMap[g];
3501 case 17 /* ESstatus */:
3503 if (this.statusString && this.statusString.charAt(0) == ';') {
3504 this.statusString = this.statusString.substr(1);
3507 window.status = this.statusString;
3510 this.isEsc = 0 /* ESnormal */;
3512 this.statusString += String.fromCharCode(ch);
3515 case 18 /* ESss2 */:
3516 case 19 /* ESss3 */:
3518 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3519 [this.toggleMeta ? (ch | 0x80) : ch];
3520 if ((ch & 0xFF00) == 0xF000) {
3522 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3523 this.isEsc = 0 /* ESnormal */; break;
3526 this.lastCharacter = String.fromCharCode(ch);
3527 lineBuf += this.lastCharacter;
3528 this.isEsc = 0 /* ESnormal */; break;
3530 this.isEsc = 0 /* ESnormal */; break;
3537 VT100.prototype.renderString = function(s, showCursor) {
3538 if (this.printing) {
3539 this.sendToPrinter(s);
3546 // We try to minimize the number of DOM operations by coalescing individual
3547 // characters into strings. This is a significant performance improvement.
3548 var incX = s.length;
3549 if (incX > this.terminalWidth - this.cursorX) {
3550 incX = this.terminalWidth - this.cursorX;
3554 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3557 // Minimize the number of calls to putString(), by avoiding a direct
3558 // call to this.showCursor()
3559 this.cursor.style.visibility = '';
3561 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3564 VT100.prototype.vt100 = function(s) {
3565 this.cursorNeedsShowing = this.hideCursor();
3566 this.respondString = '';
3568 for (var i = 0; i < s.length; i++) {
3569 var ch = s.charCodeAt(i);
3570 if (this.utfEnabled) {
3571 // Decode UTF8 encoded character
3573 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3574 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3575 if (--this.utfCount <= 0) {
3576 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3585 if ((ch & 0xE0) == 0xC0) {
3587 this.utfChar = ch & 0x1F;
3588 } else if ((ch & 0xF0) == 0xE0) {
3590 this.utfChar = ch & 0x0F;
3591 } else if ((ch & 0xF8) == 0xF0) {
3593 this.utfChar = ch & 0x07;
3594 } else if ((ch & 0xFC) == 0xF8) {
3596 this.utfChar = ch & 0x03;
3597 } else if ((ch & 0xFE) == 0xFC) {
3599 this.utfChar = ch & 0x01;
3609 var isNormalCharacter =
3610 (ch >= 32 && ch <= 127 || ch >= 160 ||
3611 this.utfEnabled && ch >= 128 ||
3612 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3613 (ch != 0x7F || this.dispCtrl);
3615 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3617 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3619 if ((ch & 0xFF00) == 0xF000) {
3621 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3624 if (!this.printing) {
3625 if (this.needWrap || this.insertMode) {
3627 this.renderString(lineBuf);
3631 if (this.needWrap) {
3632 this.cr(); this.lf();
3634 if (this.insertMode) {
3635 this.scrollRegion(this.cursorX, this.cursorY,
3636 this.terminalWidth - this.cursorX - 1, 1,
3637 1, 0, this.color, this.style);
3640 this.lastCharacter = String.fromCharCode(ch);
3641 lineBuf += this.lastCharacter;
3642 if (!this.printing &&
3643 this.cursorX + lineBuf.length >= this.terminalWidth) {
3644 this.needWrap = this.autoWrapMode;
3648 this.renderString(lineBuf);
3651 var expand = this.doControl(ch);
3652 if (expand.length) {
3653 var r = this.respondString;
3654 this.respondString= r + this.vt100(expand);
3659 this.renderString(lineBuf, this.cursorNeedsShowing);
3660 } else if (this.cursorNeedsShowing) {
3663 return this.respondString;
3666 VT100.prototype.Latin1Map = [
3667 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3668 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3669 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3670 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3671 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3672 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3673 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3674 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3675 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3676 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3677 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3678 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3679 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3680 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3681 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3682 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3683 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3684 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3685 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3686 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3687 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3688 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3689 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3690 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3691 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3692 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3693 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3694 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3695 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3696 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3697 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3698 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3701 VT100.prototype.VT100GraphicsMap = [
3702 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3703 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3704 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3705 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3706 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3707 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3708 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3709 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3710 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3711 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3712 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3713 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3714 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3715 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3716 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3717 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3718 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3719 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3720 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3721 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3722 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3723 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3724 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3725 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3726 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3727 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3728 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3729 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3730 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3731 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3732 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3733 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3736 VT100.prototype.CodePage437Map = [
3737 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3738 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3739 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3740 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3741 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3742 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3743 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3744 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3745 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3746 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3747 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3748 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3749 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3750 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3751 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3752 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3753 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3754 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3755 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3756 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3757 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3758 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3759 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3760 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3761 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3762 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3763 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3764 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3765 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3766 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3767 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3768 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3771 VT100.prototype.DirectToFontMap = [
3772 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3773 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3774 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3775 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3776 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3777 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3778 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3779 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3780 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3781 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3782 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3783 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3784 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3785 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3786 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3787 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3788 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3789 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3790 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3791 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3792 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3793 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3794 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3795 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3796 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3797 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3798 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3799 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3800 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3801 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3802 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3803 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3806 VT100.prototype.ctrlAction = [
3807 true, false, false, false, false, false, false, true,
3808 true, true, true, true, true, true, true, true,
3809 false, false, false, false, false, false, false, false,
3810 true, false, true, true, false, false, false, false
3813 VT100.prototype.ctrlAlways = [
3814 true, false, false, false, false, false, false, false,
3815 true, false, true, false, true, true, true, true,
3816 false, false, false, false, false, false, false, false,
3817 false, false, false, true, false, false, false, false