1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
71 #define ESdeviceattr 5
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
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 = 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 = ATTR_DEFAULT;
220 this.GMap = [ this.Latin1Map,
221 this.VT100GraphicsMap,
223 this.DirectToFontMap];
224 this.translate = this.GMap[this.useGMap];
226 this.bottom = this.terminalHeight;
227 this.lastCharacter = ' ';
228 this.userTabStop = [ ];
231 for (var i = 0; i < 2; i++) {
232 while (this.console[i].firstChild) {
233 this.console[i].removeChild(this.console[i].firstChild);
238 this.enableAlternateScreen(false);
240 var wasCompressed = false;
241 var transform = this.getTransformName();
243 for (var i = 0; i < 2; ++i) {
244 wasCompressed |= this.console[i].style[transform] != '';
245 this.console[i].style[transform] = '';
247 this.cursor.style[transform] = '';
248 this.space.style[transform] = '';
249 if (transform == 'filter') {
250 this.console[this.currentScreen].style.width = '';
260 this.isInverted = false;
261 this.refreshInvertedState();
262 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
263 this.color, this.style);
266 VT100.prototype.addListener = function(elem, event, listener) {
268 if (elem.addEventListener) {
269 elem.addEventListener(event, listener, false);
271 elem.attachEvent('on' + event, listener);
277 VT100.prototype.getUserSettings = function() {
278 // Compute hash signature to identify the entries in the userCSS menu.
279 // If the menu is unchanged from last time, default values can be
280 // looked up in a cookie associated with this page.
282 this.utfPreferred = true;
283 this.visualBell = typeof suppressAllAudio != 'undefined' &&
285 this.autoprint = true;
286 this.softKeyboard = false;
287 this.blinkingCursor = true;
288 if (this.visualBell) {
289 this.signature = Math.floor(16807*this.signature + 1) %
292 if (typeof userCSSList != 'undefined') {
293 for (var i = 0; i < userCSSList.length; ++i) {
294 var label = userCSSList[i][0];
295 for (var j = 0; j < label.length; ++j) {
296 this.signature = Math.floor(16807*this.signature+
297 label.charCodeAt(j)) %
300 if (userCSSList[i][1]) {
301 this.signature = Math.floor(16807*this.signature + 1) %
307 var key = 'shellInABox=' + this.signature + ':';
308 var settings = document.cookie.indexOf(key);
310 settings = document.cookie.substr(settings + key.length).
311 replace(/([0-1]*).*/, "$1");
312 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
313 0 : userCSSList.length)) {
314 this.utfPreferred = settings.charAt(0) != '0';
315 this.visualBell = settings.charAt(1) != '0';
316 this.autoprint = settings.charAt(2) != '0';
317 this.softKeyboard = settings.charAt(3) != '0';
318 this.blinkingCursor = settings.charAt(4) != '0';
319 if (typeof userCSSList != 'undefined') {
320 for (var i = 0; i < userCSSList.length; ++i) {
321 userCSSList[i][2] = settings.charAt(i + 5) != '0';
326 this.utfEnabled = this.utfPreferred;
329 VT100.prototype.storeUserSettings = function() {
330 var settings = 'shellInABox=' + this.signature + ':' +
331 (this.utfEnabled ? '1' : '0') +
332 (this.visualBell ? '1' : '0') +
333 (this.autoprint ? '1' : '0') +
334 (this.softKeyboard ? '1' : '0') +
335 (this.blinkingCursor ? '1' : '0');
336 if (typeof userCSSList != 'undefined') {
337 for (var i = 0; i < userCSSList.length; ++i) {
338 settings += userCSSList[i][2] ? '1' : '0';
342 d.setDate(d.getDate() + 3653);
343 document.cookie = settings + ';expires=' + d.toGMTString();
346 VT100.prototype.initializeUserCSSStyles = function() {
347 this.usercssActions = [];
348 if (typeof userCSSList != 'undefined') {
351 var wasSingleSel = 1;
352 var beginOfGroup = 0;
353 for (var i = 0; i <= userCSSList.length; ++i) {
354 if (i < userCSSList.length) {
355 var label = userCSSList[i][0];
356 var newGroup = userCSSList[i][1];
357 var enabled = userCSSList[i][2];
359 // Add user style sheet to document
360 var style = document.createElement('link');
361 var id = document.createAttribute('id');
362 id.nodeValue = 'usercss-' + i;
363 style.setAttributeNode(id);
364 var rel = document.createAttribute('rel');
365 rel.nodeValue = 'stylesheet';
366 style.setAttributeNode(rel);
367 var href = document.createAttribute('href');
368 href.nodeValue = 'usercss-' + i + '.css';
369 style.setAttributeNode(href);
370 var type = document.createAttribute('type');
371 type.nodeValue = 'text/css';
372 style.setAttributeNode(type);
373 document.getElementsByTagName('head')[0].appendChild(style);
374 style.disabled = !enabled;
378 if (newGroup || i == userCSSList.length) {
379 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
380 // The last group had multiple entries that are mutually exclusive;
381 // or the previous to last group did. In either case, we need to
382 // append a "<hr />" before we can add the last group to the menu.
385 wasSingleSel = i - beginOfGroup < 1;
389 for (var j = beginOfGroup; j < i; ++j) {
390 this.usercssActions[this.usercssActions.length] =
391 function(vt100, current, begin, count) {
393 // Deselect all other entries in the group, then either select
394 // (for multiple entries in group) or toggle (for on/off entry)
395 // the current entry.
397 var entry = vt100.getChildById(vt100.menu,
401 for (var c = count; c > 0; ++j) {
402 if (entry.tagName == 'LI') {
405 var label = vt100.usercss.childNodes[j];
407 // Restore label to just the text content
408 if (typeof label.textContent == 'undefined') {
409 var s = label.innerText;
410 label.innerHTML = '';
411 label.appendChild(document.createTextNode(s));
413 label.textContent= label.textContent;
416 // User style sheets are numbered sequentially
417 var sheet = document.getElementById(
421 sheet.disabled = !sheet.disabled;
423 sheet.disabled = false;
425 if (!sheet.disabled) {
426 label.innerHTML= '<img src="enabled.gif" />' +
430 sheet.disabled = true;
432 userCSSList[i][2] = !sheet.disabled;
435 entry = entry.nextSibling;
438 // If the font size changed, adjust cursor and line dimensions
439 this.cursor.style.cssText= '';
440 this.cursorWidth = this.cursor.clientWidth;
441 this.cursorHeight = this.lineheight.clientHeight;
442 for (i = 0; i < this.console.length; ++i) {
443 for (var line = this.console[i].firstChild; line;
444 line = line.nextSibling) {
445 line.style.height = this.cursorHeight + 'px';
450 }(this, j, beginOfGroup, i - beginOfGroup);
453 if (i == userCSSList.length) {
459 // Collect all entries in a group, before attaching them to the menu.
460 // This is necessary as we don't know whether this is a group of
461 // mutually exclusive options (which should be separated by "<hr />" on
462 // both ends), or whether this is a on/off toggle, which can be grouped
463 // together with other on/off options.
465 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
469 this.usercss.innerHTML = menu;
473 VT100.prototype.resetLastSelectedKey = function(e) {
474 var key = this.lastSelectedKey;
479 var position = this.mousePosition(e);
481 // We don't get all the necessary events to reliably reselect a key
482 // if we moved away from it and then back onto it. We approximate the
483 // behavior by remembering the key until either we release the mouse
484 // button (we might never get this event if the mouse has since left
485 // the window), or until we move away too far.
486 var box = this.keyboard.firstChild;
487 if (position[0] < box.offsetLeft + key.offsetWidth ||
488 position[1] < box.offsetTop + key.offsetHeight ||
489 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
490 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
491 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
492 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
493 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
494 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
495 if (this.lastSelectedKey.className) log.console('reset: deselecting');
496 this.lastSelectedKey.className = '';
497 this.lastSelectedKey = undefined;
502 VT100.prototype.showShiftState = function(state) {
503 var style = document.getElementById('shift_state');
505 this.setTextContentRaw(style,
506 '#vt100 #keyboard .shifted {' +
507 'display: inline }' +
508 '#vt100 #keyboard .unshifted {' +
511 this.setTextContentRaw(style, '');
513 var elems = this.keyboard.getElementsByTagName('I');
514 for (var i = 0; i < elems.length; ++i) {
515 if (elems[i].id == '16') {
516 elems[i].className = state ? 'selected' : '';
521 VT100.prototype.showCtrlState = function(state) {
522 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
524 ctrl.className = state ? 'selected' : '';
528 VT100.prototype.showAltState = function(state) {
529 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
531 alt.className = state ? 'selected' : '';
535 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
540 fake.shiftKey = shift;
543 return this.handleKey(fake);
546 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
547 if (elem == undefined) {
550 if (ch == '\u00A0') {
551 // should be treated as a regular space character.
554 if (ch != undefined && CH == undefined) {
555 // For letter keys, we automatically compute the uppercase character code
556 // from the lowercase one.
557 CH = ch.toUpperCase();
559 if (KEY == undefined && key != undefined) {
560 // Most keys have identically key codes for both lowercase and uppercase
561 // keypresses. Normally, only function keys would have distinct key codes,
562 // whereas regular keys have character codes.
564 } else if (KEY == undefined && CH != undefined) {
565 // For regular keys, copy the character code to the key code.
566 KEY = CH.charCodeAt(0);
568 if (key == undefined && ch != undefined) {
569 // For regular keys, copy the character code to the key code.
570 key = ch.charCodeAt(0);
572 // Convert characters to numeric character codes. If the character code
573 // is undefined (i.e. this is a function key), set it to zero.
574 ch = ch ? ch.charCodeAt(0) : 0;
575 CH = CH ? CH.charCodeAt(0) : 0;
577 // Mouse down events high light the key. We also set lastSelectedKey. This
578 // is needed to that mouseout/mouseover can keep track of the key that
579 // is currently being clicked.
580 this.addListener(elem, 'mousedown',
581 function(vt100, elem, key) { return function(e) {
582 if ((e.which || e.button) == 1) {
583 if (vt100.lastSelectedKey) {
584 vt100.lastSelectedKey.className= '';
586 // Highlight the key while the mouse button is held down.
587 if (key == 16 /* Shift */) {
588 if (!elem.className != vt100.isShift) {
589 vt100.showShiftState(!vt100.isShift);
591 } else if (key == 17 /* Ctrl */) {
592 if (!elem.className != vt100.isCtrl) {
593 vt100.showCtrlState(!vt100.isCtrl);
595 } else if (key == 18 /* Alt */) {
596 if (!elem.className != vt100.isAlt) {
597 vt100.showAltState(!vt100.isAlt);
600 elem.className = 'selected';
602 vt100.lastSelectedKey = elem;
604 return false; }; }(this, elem, key));
606 // Modifier keys update the state of the keyboard, but do not generate
607 // any key clicks that get forwarded to the application.
608 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
609 function(vt100, elem) { return function(e) {
610 if (elem == vt100.lastSelectedKey) {
611 if (key == 16 /* Shift */) {
612 // The user clicked the Shift key
613 vt100.isShift = !vt100.isShift;
614 vt100.showShiftState(vt100.isShift);
615 } else if (key == 17 /* Ctrl */) {
616 vt100.isCtrl = !vt100.isCtrl;
617 vt100.showCtrlState(vt100.isCtrl);
618 } else if (key == 18 /* Alt */) {
619 vt100.isAlt = !vt100.isAlt;
620 vt100.showAltState(vt100.isAlt);
622 vt100.lastSelectedKey = undefined;
624 if (vt100.lastSelectedKey) {
625 vt100.lastSelectedKey.className = '';
626 vt100.lastSelectedKey = undefined;
628 return false; }; }(this, elem) :
629 // Regular keys generate key clicks, when the mouse button is released or
630 // when a mouse click event is received.
631 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
632 if (vt100.lastSelectedKey) {
633 if (elem == vt100.lastSelectedKey) {
634 // The user clicked a key.
636 vt100.clickedKeyboard(e, elem, CH, KEY,
637 true, vt100.isCtrl, vt100.isAlt);
639 vt100.clickedKeyboard(e, elem, ch, key,
640 false, vt100.isCtrl, vt100.isAlt);
642 vt100.isShift = false;
643 vt100.showShiftState(false);
644 vt100.isCtrl = false;
645 vt100.showCtrlState(false);
647 vt100.showAltState(false);
649 vt100.lastSelectedKey.className = '';
650 vt100.lastSelectedKey = undefined;
653 return false; }; }(this, elem, ch, key, CH, KEY);
654 this.addListener(elem, 'mouseup', clicked);
655 this.addListener(elem, 'click', clicked);
657 // When moving the mouse away from a key, check if any keys need to be
659 this.addListener(elem, 'mouseout',
660 function(vt100, elem, key) { return function(e) {
661 if (key == 16 /* Shift */) {
662 if (!elem.className == vt100.isShift) {
663 vt100.showShiftState(vt100.isShift);
665 } else if (key == 17 /* Ctrl */) {
666 if (!elem.className == vt100.isCtrl) {
667 vt100.showCtrlState(vt100.isCtrl);
669 } else if (key == 18 /* Alt */) {
670 if (!elem.className == vt100.isAlt) {
671 vt100.showAltState(vt100.isAlt);
673 } else if (elem.className) {
675 vt100.lastSelectedKey = elem;
676 } else if (vt100.lastSelectedKey) {
677 vt100.resetLastSelectedKey(e);
679 return false; }; }(this, elem, key));
681 // When moving the mouse over a key, select it if the user is still holding
682 // the mouse button down (i.e. elem == lastSelectedKey)
683 this.addListener(elem, 'mouseover',
684 function(vt100, elem, key) { return function(e) {
685 if (elem == vt100.lastSelectedKey) {
686 if (key == 16 /* Shift */) {
687 if (!elem.className != vt100.isShift) {
688 vt100.showShiftState(!vt100.isShift);
690 } else if (key == 17 /* Ctrl */) {
691 if (!elem.className != vt100.isCtrl) {
692 vt100.showCtrlState(!vt100.isCtrl);
694 } else if (key == 18 /* Alt */) {
695 if (!elem.className != vt100.isAlt) {
696 vt100.showAltState(!vt100.isAlt);
698 } else if (!elem.className) {
699 elem.className = 'selected';
702 vt100.resetLastSelectedKey(e);
704 return false; }; }(this, elem, key));
707 VT100.prototype.initializeKeyBindings = function(elem) {
709 if (elem.nodeName == "I" || elem.nodeName == "B") {
711 // Function keys. The Javascript keycode is part of the "id"
712 var i = parseInt(elem.id);
714 // If the id does not parse as a number, it is not a keycode.
715 this.addKeyBinding(elem, undefined, i);
718 var child = elem.firstChild;
719 if (child.nodeName == "#text") {
720 // If the key only has a text node as a child, then it is a letter.
721 // Automatically compute the lower and upper case version of the key.
722 this.addKeyBinding(elem, this.getTextContent(child).toLowerCase());
724 // If the key has two children, they are the lower and upper case
725 // character code, respectively.
726 this.addKeyBinding(elem, this.getTextContent(child), undefined,
727 this.getTextContent(child.nextSibling));
732 // Recursively parse all other child nodes.
733 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
734 this.initializeKeyBindings(elem);
738 VT100.prototype.initializeKeyboard = function() {
739 // Configure mouse event handlers for button that displays/hides keyboard
740 var box = this.keyboard.firstChild;
741 this.hideSoftKeyboard();
742 this.addListener(this.keyboardImage, 'click',
743 function(vt100) { return function(e) {
744 if (vt100.keyboard.style.display != '') {
745 if (vt100.reconnectBtn.style.visibility != '') {
746 vt100.showSoftKeyboard();
749 vt100.hideSoftKeyboard();
752 return false; }; }(this));
754 // Enable button that displays keyboard
755 if (this.softKeyboard) {
756 this.keyboardImage.style.visibility = 'visible';
759 // Configure mouse event handlers for on-screen keyboard
760 this.addListener(this.keyboard, 'click',
761 function(vt100) { return function(e) {
762 vt100.hideSoftKeyboard();
764 return false; }; }(this));
765 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
766 this.addListener(box, 'click', this.cancelEvent);
767 this.addListener(box, 'mouseup',
768 function(vt100) { return function(e) {
769 if (vt100.lastSelectedKey) {
770 vt100.lastSelectedKey.className = '';
771 vt100.lastSelectedKey = undefined;
773 return false; }; }(this));
774 this.addListener(box, 'mouseout',
775 function(vt100) { return function(e) {
776 return vt100.resetLastSelectedKey(e); }; }(this));
777 this.addListener(box, 'mouseover',
778 function(vt100) { return function(e) {
779 return vt100.resetLastSelectedKey(e); }; }(this));
781 // Configure SHIFT key behavior
782 var style = document.createElement('style');
783 var id = document.createAttribute('id');
784 id.nodeValue = 'shift_state';
785 style.setAttributeNode(id);
786 var type = document.createAttribute('type');
787 type.nodeValue = 'text/css';
788 style.setAttributeNode(type);
789 document.getElementsByTagName('head')[0].appendChild(style);
791 // Set up key bindings
792 this.initializeKeyBindings(box);
795 VT100.prototype.initializeElements = function(container) {
796 // If the necessary objects have not already been defined in the HTML
797 // page, create them now.
799 this.container = container;
800 } else if (!(this.container = document.getElementById('vt100'))) {
801 this.container = document.createElement('div');
802 this.container.id = 'vt100';
803 document.body.appendChild(this.container);
806 if (!this.getChildById(this.container, 'reconnect') ||
807 !this.getChildById(this.container, 'menu') ||
808 !this.getChildById(this.container, 'keyboard') ||
809 !this.getChildById(this.container, 'kbd_button') ||
810 !this.getChildById(this.container, 'kbd_img') ||
811 !this.getChildById(this.container, 'scrollable') ||
812 !this.getChildById(this.container, 'console') ||
813 !this.getChildById(this.container, 'alt_console') ||
814 !this.getChildById(this.container, 'ieprobe') ||
815 !this.getChildById(this.container, 'padding') ||
816 !this.getChildById(this.container, 'cursor') ||
817 !this.getChildById(this.container, 'lineheight') ||
818 !this.getChildById(this.container, 'usercss') ||
819 !this.getChildById(this.container, 'space') ||
820 !this.getChildById(this.container, 'input') ||
821 !this.getChildById(this.container, 'cliphelper')) {
822 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
823 // we might get a pointless warning that a suitable plugin is not yet
824 // installed. If in doubt, we'd rather just stay silent.
827 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
829 embed = typeof suppressAllAudio != 'undefined' &&
830 suppressAllAudio ? "" :
831 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
834 'autostart="false" ' +
836 'enablejavascript="true" ' +
837 'type="audio/x-wav" ' +
840 'style="position:absolute;left:-1000px;top:-1000px" />';
845 this.container.innerHTML =
846 '<div id="reconnect" style="visibility: hidden">' +
847 '<input type="button" value="Connect" ' +
848 'onsubmit="return false" />' +
850 '<div id="cursize" style="visibility: hidden">' +
852 '<div id="menu"></div>' +
853 '<div id="keyboard" unselectable="on">' +
856 '<div id="scrollable">' +
857 '<table id="kbd_button">' +
858 '<tr><td width="100%"> </td>' +
859 '<td><img id="kbd_img" src="keyboard.png" /></td>' +
860 '<td> </td></tr>' +
862 '<pre id="lineheight"> </pre>' +
863 '<pre id="console">' +
865 '<div id="ieprobe"><span> </span></div>' +
867 '<pre id="alt_console" style="display: none"></pre>' +
868 '<div id="padding"></div>' +
869 '<pre id="cursor"> </pre>' +
871 '<div class="hidden">' +
872 '<div id="usercss"></div>' +
873 '<pre><div><span id="space"></span></div></pre>' +
874 '<input type="textfield" id="input" />' +
875 '<input type="textfield" id="cliphelper" />' +
876 (typeof suppressAllAudio != 'undefined' &&
877 suppressAllAudio ? "" :
878 embed + '<bgsound id="beep_bgsound" loop=1 />') +
882 // Find the object used for playing the "beep" sound, if any.
883 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
884 this.beeper = undefined;
886 this.beeper = this.getChildById(this.container,
888 if (!this.beeper || !this.beeper.Play) {
889 this.beeper = this.getChildById(this.container,
891 if (!this.beeper || typeof this.beeper.src == 'undefined') {
892 this.beeper = undefined;
897 // Initialize the variables for finding the text console and the
899 this.reconnectBtn = this.getChildById(this.container,'reconnect');
900 this.curSizeBox = this.getChildById(this.container, 'cursize');
901 this.menu = this.getChildById(this.container, 'menu');
902 this.keyboard = this.getChildById(this.container, 'keyboard');
903 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
904 this.scrollable = this.getChildById(this.container,
906 this.lineheight = this.getChildById(this.container,
909 [ this.getChildById(this.container, 'console'),
910 this.getChildById(this.container, 'alt_console') ];
911 var ieProbe = this.getChildById(this.container, 'ieprobe');
912 this.padding = this.getChildById(this.container, 'padding');
913 this.cursor = this.getChildById(this.container, 'cursor');
914 this.usercss = this.getChildById(this.container, 'usercss');
915 this.space = this.getChildById(this.container, 'space');
916 this.input = this.getChildById(this.container, 'input');
917 this.cliphelper = this.getChildById(this.container,
920 // Add any user selectable style sheets to the menu
921 this.initializeUserCSSStyles();
923 // Remember the dimensions of a standard character glyph. We would
924 // expect that we could just check cursor.clientWidth/Height at any time,
925 // but it turns out that browsers sometimes invalidate these values
926 // (e.g. while displaying a print preview screen).
927 this.cursorWidth = this.cursor.clientWidth;
928 this.cursorHeight = this.lineheight.clientHeight;
930 // IE has a slightly different boxing model, that we need to compensate for
931 this.isIE = ieProbe.offsetTop > 1;
933 this.console.innerHTML = '';
935 // Determine if the terminal window is positioned at the beginning of the
936 // page, or if it is embedded somewhere else in the page. For full-screen
937 // terminals, automatically resize whenever the browser window changes.
938 var marginTop = parseInt(this.getCurrentComputedStyle(
939 document.body, 'marginTop'));
940 var marginLeft = parseInt(this.getCurrentComputedStyle(
941 document.body, 'marginLeft'));
942 var marginRight = parseInt(this.getCurrentComputedStyle(
943 document.body, 'marginRight'));
944 var x = this.container.offsetLeft;
945 var y = this.container.offsetTop;
946 for (var parent = this.container; parent = parent.offsetParent; ) {
947 x += parent.offsetLeft;
948 y += parent.offsetTop;
950 this.isEmbedded = marginTop != y ||
952 (window.innerWidth ||
953 document.documentElement.clientWidth ||
954 document.body.clientWidth) -
955 marginRight != x + this.container.offsetWidth;
956 if (!this.isEmbedded) {
957 // Some browsers generate resize events when the terminal is first
958 // shown. Disable showing the size indicator until a little bit after
959 // the terminal has been rendered the first time.
960 this.indicateSize = false;
961 setTimeout(function(vt100) {
963 vt100.indicateSize = true;
966 this.addListener(window, 'resize',
969 vt100.hideContextMenu();
971 vt100.showCurrentSize();
975 // Hide extra scrollbars attached to window
976 document.body.style.margin = '0px';
977 try { document.body.style.overflow ='hidden'; } catch (e) { }
978 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
982 this.hideContextMenu();
984 // Set up onscreen soft keyboard
985 this.initializeKeyboard();
987 // Add listener to reconnect button
988 this.addListener(this.reconnectBtn.firstChild, 'click',
991 var rc = vt100.reconnect();
997 // Add input listeners
998 this.addListener(this.input, 'blur',
1000 return function() { vt100.blurCursor(); } }(this));
1001 this.addListener(this.input, 'focus',
1003 return function() { vt100.focusCursor(); } }(this));
1004 this.addListener(this.input, 'keydown',
1006 return function(e) {
1007 if (!e) e = window.event;
1008 return vt100.keyDown(e); } }(this));
1009 this.addListener(this.input, 'keypress',
1011 return function(e) {
1012 if (!e) e = window.event;
1013 return vt100.keyPressed(e); } }(this));
1014 this.addListener(this.input, 'keyup',
1016 return function(e) {
1017 if (!e) e = window.event;
1018 return vt100.keyUp(e); } }(this));
1020 // Attach listeners that move the focus to the <input> field. This way we
1021 // can make sure that we can receive keyboard input.
1022 var mouseEvent = function(vt100, type) {
1023 return function(e) {
1024 if (!e) e = window.event;
1025 return vt100.mouseEvent(e, type);
1028 this.addListener(this.scrollable,'mousedown',mouseEvent(this, MOUSE_DOWN));
1029 this.addListener(this.scrollable,'mouseup', mouseEvent(this, MOUSE_UP));
1030 this.addListener(this.scrollable,'click', mouseEvent(this, MOUSE_CLICK));
1032 // Initialize the blank terminal window.
1033 this.currentScreen = 0;
1036 this.numScrollbackLines = 0;
1038 this.bottom = 0x7FFFFFFF;
1045 VT100.prototype.getChildById = function(parent, id) {
1046 var nodeList = parent.all || parent.getElementsByTagName('*');
1047 if (typeof nodeList.namedItem == 'undefined') {
1048 for (var i = 0; i < nodeList.length; i++) {
1049 if (nodeList[i].id == id) {
1055 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1056 return elem ? elem[0] || elem : null;
1060 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1061 if (typeof elem.currentStyle != 'undefined') {
1062 return elem.currentStyle[style];
1064 return document.defaultView.getComputedStyle(elem, null)[style];
1068 VT100.prototype.reconnect = function() {
1072 VT100.prototype.showReconnect = function(state) {
1074 this.hideSoftKeyboard();
1075 this.reconnectBtn.style.visibility = '';
1077 this.reconnectBtn.style.visibility = 'hidden';
1081 VT100.prototype.repairElements = function(console) {
1082 for (var line = console.firstChild; line; line = line.nextSibling) {
1083 if (!line.clientHeight) {
1084 var newLine = document.createElement(line.tagName);
1085 newLine.style.cssText = line.style.cssText;
1086 newLine.className = line.className;
1087 if (line.tagName == 'DIV') {
1088 for (var span = line.firstChild; span; span = span.nextSibling) {
1089 var newSpan = document.createElement(span.tagName);
1090 newSpan.style.cssText = span.style.cssText;
1091 newSpan.style.className = span.style.className;
1092 this.setTextContent(newSpan, this.getTextContent(span));
1093 newLine.appendChild(newSpan);
1096 this.setTextContent(newLine, this.getTextContent(line));
1098 line.parentNode.replaceChild(newLine, line);
1104 VT100.prototype.resized = function(w, h) {
1107 VT100.prototype.resizer = function() {
1108 // Hide onscreen soft keyboard
1109 this.hideSoftKeyboard();
1111 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1112 // Recreating it, will repair it.
1113 var newCursor = document.createElement('pre');
1114 this.setTextContent(newCursor, ' ');
1115 newCursor.id = 'cursor';
1116 newCursor.style.cssText = this.cursor.style.cssText;
1117 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1118 if (!newCursor.clientHeight) {
1119 // Things are broken right now. This is probably because we are
1120 // displaying the print-preview. Just don't change any of our settings
1121 // until the print dialog is closed again.
1122 newCursor.parentNode.removeChild(newCursor);
1125 // Swap the old broken cursor for the newly created one.
1126 this.cursor.parentNode.removeChild(this.cursor);
1127 this.cursor = newCursor;
1130 // Really horrible things happen if the contents of the terminal changes
1131 // while the print-preview is showing. We get HTML elements that show up
1132 // in the DOM, but that do not take up any space. Find these elements and
1134 this.repairElements(this.console[0]);
1135 this.repairElements(this.console[1]);
1137 // Lock the cursor size to the size of a normal character. This helps with
1138 // characters that are taller/shorter than normal. Unfortunately, we will
1139 // still get confused if somebody enters a character that is wider/narrower
1140 // than normal. This can happen if the browser tries to substitute a
1141 // characters from a different font.
1142 this.cursor.style.width = this.cursorWidth + 'px';
1143 this.cursor.style.height = this.cursorHeight + 'px';
1145 // Adjust height for one pixel padding of the #vt100 element.
1146 // The latter is necessary to properly display the inactive cursor.
1147 var console = this.console[this.currentScreen];
1148 var height = (this.isEmbedded ? this.container.clientHeight
1149 : (window.innerHeight ||
1150 document.documentElement.clientHeight ||
1151 document.body.clientHeight))-1;
1152 var partial = height % this.cursorHeight;
1153 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1154 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1155 var oldTerminalHeight = this.terminalHeight;
1157 this.updateHeight();
1159 // Clip the cursor to the visible screen.
1160 var cx = this.cursorX;
1161 var cy = this.cursorY + this.numScrollbackLines;
1163 // The alternate screen never keeps a scroll back buffer.
1164 this.updateNumScrollbackLines();
1165 while (this.currentScreen && this.numScrollbackLines > 0) {
1166 console.removeChild(console.firstChild);
1167 this.numScrollbackLines--;
1169 cy -= this.numScrollbackLines;
1172 } else if (cx > this.terminalWidth) {
1173 cx = this.terminalWidth - 1;
1180 } else if (cy > this.terminalHeight) {
1181 cy = this.terminalHeight - 1;
1187 // Clip the scroll region to the visible screen.
1188 if (this.bottom > this.terminalHeight ||
1189 this.bottom == oldTerminalHeight) {
1190 this.bottom = this.terminalHeight;
1192 if (this.top >= this.bottom) {
1193 this.top = this.bottom-1;
1199 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1200 // particularly important after changing the screen number), and reset
1201 // the scroll region to the default.
1202 this.truncateLines(this.terminalWidth);
1203 this.putString(cx, cy, '', undefined);
1204 this.scrollable.scrollTop = this.numScrollbackLines *
1205 this.cursorHeight + 1;
1207 // Update classNames for lines in the scrollback buffer
1208 var line = console.firstChild;
1209 for (var i = 0; i < this.numScrollbackLines; i++) {
1210 line.className = 'scrollback';
1211 line = line.nextSibling;
1214 line.className = '';
1215 line = line.nextSibling;
1218 // Reposition the reconnect button
1219 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1221 this.reconnectBtn.clientWidth)/2 + 'px';
1222 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1223 this.reconnectBtn.clientHeight)/2 + 'px';
1225 // Send notification that the window size has been changed
1226 this.resized(this.terminalWidth, this.terminalHeight);
1229 VT100.prototype.showCurrentSize = function() {
1230 if (!this.indicateSize) {
1233 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1234 this.terminalHeight;
1235 this.curSizeBox.style.left =
1236 (this.terminalWidth*this.cursorWidth/
1238 this.curSizeBox.clientWidth)/2 + 'px';
1239 this.curSizeBox.style.top =
1240 (this.terminalHeight*this.cursorHeight -
1241 this.curSizeBox.clientHeight)/2 + 'px';
1242 this.curSizeBox.style.visibility = '';
1243 if (this.curSizeTimeout) {
1244 clearTimeout(this.curSizeTimeout);
1247 // Only show the terminal size for a short amount of time after resizing.
1248 // Then hide this information, again. Some browsers generate resize events
1249 // throughout the entire resize operation. This is nice, and we will show
1250 // the terminal size while the user is dragging the window borders.
1251 // Other browsers only generate a single event when the user releases the
1252 // mouse. In those cases, we can only show the terminal size once at the
1253 // end of the resize operation.
1254 this.curSizeTimeout = setTimeout(function(vt100) {
1256 vt100.curSizeTimeout = null;
1257 vt100.curSizeBox.style.visibility = 'hidden';
1262 VT100.prototype.selection = function() {
1264 return '' + (window.getSelection && window.getSelection() ||
1265 document.selection && document.selection.type == 'Text' &&
1266 document.selection.createRange().text || '');
1272 VT100.prototype.cancelEvent = function(event) {
1274 // For non-IE browsers
1275 event.stopPropagation();
1276 event.preventDefault();
1281 event.cancelBubble = true;
1282 event.returnValue = false;
1290 VT100.prototype.mousePosition = function(event) {
1291 var offsetX = this.container.offsetLeft;
1292 var offsetY = this.container.offsetTop;
1293 for (var e = this.container; e = e.offsetParent; ) {
1294 offsetX += e.offsetLeft;
1295 offsetY += e.offsetTop;
1297 return [ event.clientX - offsetX,
1298 event.clientY - offsetY ];
1301 VT100.prototype.mouseEvent = function(event, type) {
1302 // If any text is currently selected, do not move the focus as that would
1303 // invalidate the selection.
1304 var selection = this.selection();
1305 if ((type == MOUSE_UP || type == MOUSE_CLICK) && !selection.length) {
1309 // Compute mouse position in characters.
1310 var position = this.mousePosition(event);
1311 var x = Math.floor(position[0] / this.cursorWidth);
1312 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1313 this.cursorHeight) - this.numScrollbackLines;
1315 if (x >= this.terminalWidth) {
1316 x = this.terminalWidth - 1;
1323 if (y >= this.terminalHeight) {
1324 y = this.terminalHeight - 1;
1332 // Compute button number and modifier keys.
1333 var button = type != MOUSE_DOWN ? 3 :
1334 typeof event.pageX != 'undefined' ? event.button :
1335 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1336 if (button != undefined) {
1337 if (event.shiftKey) {
1340 if (event.altKey || event.metaKey) {
1343 if (event.ctrlKey) {
1348 // Report mouse events if they happen inside of the current screen and
1349 // with the SHIFT key unpressed. Both of these restrictions do not apply
1350 // for button releases, as we always want to report those.
1351 if (this.mouseReporting && !selection.length &&
1352 (type != MOUSE_DOWN || !event.shiftKey)) {
1353 if (inside || type != MOUSE_DOWN) {
1354 if (button != undefined) {
1355 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1356 String.fromCharCode(x + 33) +
1357 String.fromCharCode(y + 33);
1358 if (type != MOUSE_CLICK) {
1359 this.keysPressed(report);
1362 // If we reported the event, stop propagating it (not sure, if this
1363 // actually works on most browsers; blocking the global "oncontextmenu"
1364 // even is still necessary).
1365 return this.cancelEvent(event);
1370 // Bring up context menu.
1371 if (button == 2 && !event.shiftKey) {
1372 if (type == MOUSE_DOWN) {
1373 this.showContextMenu(position[0], position[1]);
1375 return this.cancelEvent(event);
1378 if (this.mouseReporting) {
1380 event.shiftKey = false;
1388 VT100.prototype.replaceChar = function(s, ch, repl) {
1389 for (var i = -1;;) {
1390 i = s.indexOf(ch, i + 1);
1394 s = s.substr(0, i) + repl + s.substr(i + 1);
1399 VT100.prototype.htmlEscape = function(s) {
1400 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1401 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1404 VT100.prototype.getTextContent = function(elem) {
1405 return elem.textContent ||
1406 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1409 VT100.prototype.setTextContentRaw = function(elem, s) {
1410 // Updating the content of an element is an expensive operation. It actually
1411 // pays off to first check whether the element is still unchanged.
1412 if (typeof elem.textContent == 'undefined') {
1413 if (elem.innerText != s) {
1417 // Very old versions of IE do not allow setting innerText. Instead,
1418 // remove all children, by setting innerHTML and then set the text
1419 // using DOM methods.
1420 elem.innerHTML = '';
1421 elem.appendChild(document.createTextNode(
1422 this.replaceChar(s, ' ', '\u00A0')));
1426 if (elem.textContent != s) {
1427 elem.textContent = s;
1432 VT100.prototype.setTextContent = function(elem, s) {
1433 // Check if we find any URLs in the text. If so, automatically convert them
1435 if (this.urlRE && this.urlRE.test(s)) {
1439 if (RegExp.leftContext != null) {
1440 inner += this.htmlEscape(RegExp.leftContext);
1441 consumed += RegExp.leftContext.length;
1443 var url = this.htmlEscape(RegExp.lastMatch);
1446 // If no protocol was specified, try to guess a reasonable one.
1447 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1448 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1449 var slash = url.indexOf('/');
1450 var at = url.indexOf('@');
1451 var question = url.indexOf('?');
1453 (at < question || question < 0) &&
1454 (slash < 0 || (question > 0 && slash > question))) {
1455 fullUrl = 'mailto:' + url;
1457 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1462 inner += '<a target="vt100Link" href="' + fullUrl +
1463 '">' + url + '</a>';
1464 consumed += RegExp.lastMatch.length;
1465 s = s.substr(consumed);
1466 if (!this.urlRE.test(s)) {
1467 if (RegExp.rightContext != null) {
1468 inner += this.htmlEscape(RegExp.rightContext);
1473 elem.innerHTML = inner;
1477 this.setTextContentRaw(elem, s);
1480 VT100.prototype.insertBlankLine = function(y, color, style) {
1481 // Insert a blank line a position y. This method ignores the scrollback
1482 // buffer. The caller has to add the length of the scrollback buffer to
1483 // the position, if necessary.
1484 // If the position is larger than the number of current lines, this
1485 // method just adds a new line right after the last existing one. It does
1486 // not add any missing lines in between. It is the caller's responsibility
1489 color = 'ansi0 bgAnsi15';
1495 if (color != 'ansi0 bgAnsi15' && !style) {
1496 line = document.createElement('pre');
1497 this.setTextContent(line, '\n');
1499 line = document.createElement('div');
1500 var span = document.createElement('span');
1501 span.style.cssText = style;
1502 span.style.className = color;
1503 this.setTextContent(span, this.spaces(this.terminalWidth));
1504 line.appendChild(span);
1506 line.style.height = this.cursorHeight + 'px';
1507 var console = this.console[this.currentScreen];
1508 if (console.childNodes.length > y) {
1509 console.insertBefore(line, console.childNodes[y]);
1511 console.appendChild(line);
1515 VT100.prototype.updateWidth = function() {
1516 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1517 this.cursorWidth*this.scale);
1518 return this.terminalWidth;
1521 VT100.prototype.updateHeight = function() {
1522 // We want to be able to display either a terminal window that fills the
1523 // entire browser window, or a terminal window that is contained in a
1524 // <div> which is embededded somewhere in the web page.
1525 if (this.isEmbedded) {
1526 // Embedded terminal. Use size of the containing <div> (id="vt100").
1527 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1530 // Use the full browser window.
1531 this.terminalHeight = Math.floor(((window.innerHeight ||
1532 document.documentElement.clientHeight ||
1533 document.body.clientHeight)-1)/
1536 return this.terminalHeight;
1539 VT100.prototype.updateNumScrollbackLines = function() {
1540 var scrollback = Math.floor(
1541 this.console[this.currentScreen].offsetHeight /
1542 this.cursorHeight) -
1543 this.terminalHeight;
1544 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1545 return this.numScrollbackLines;
1548 VT100.prototype.truncateLines = function(width) {
1552 for (var line = this.console[this.currentScreen].firstChild; line;
1553 line = line.nextSibling) {
1554 if (line.tagName == 'DIV') {
1557 // Traverse current line and truncate it once we saw "width" characters
1558 for (var span = line.firstChild; span;
1559 span = span.nextSibling) {
1560 var s = this.getTextContent(span);
1562 if (x + l > width) {
1563 this.setTextContent(span, s.substr(0, width - x));
1564 while (span.nextSibling) {
1565 line.removeChild(line.lastChild);
1571 // Prune white space from the end of the current line
1572 var span = line.lastChild;
1574 span.className == 'ansi0 bgAnsi15' &&
1575 !span.style.cssText.length) {
1576 // Scan backwards looking for first non-space character
1577 var s = this.getTextContent(span);
1578 for (var i = s.length; i--; ) {
1579 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1580 if (i+1 != s.length) {
1581 this.setTextContent(s.substr(0, i+1));
1589 span = span.previousSibling;
1591 // Remove blank <span>'s from end of line
1592 line.removeChild(sibling);
1594 // Remove entire line (i.e. <div>), if empty
1595 var blank = document.createElement('pre');
1596 blank.style.height = this.cursorHeight + 'px';
1597 this.setTextContent(blank, '\n');
1598 line.parentNode.replaceChild(blank, line);
1606 VT100.prototype.putString = function(x, y, text, color, style) {
1608 color = 'ansi0 bgAnsi15';
1613 var yIdx = y + this.numScrollbackLines;
1619 var console = this.console[this.currentScreen];
1620 if (!text.length && (yIdx >= console.childNodes.length ||
1621 console.childNodes[yIdx].tagName != 'DIV')) {
1622 // Positioning cursor to a blank location
1625 // Create missing blank lines at end of page
1626 while (console.childNodes.length <= yIdx) {
1627 // In order to simplify lookups, we want to make sure that each line
1628 // is represented by exactly one element (and possibly a whole bunch of
1630 // For non-blank lines, we can create a <div> containing one or more
1631 // <span>s. For blank lines, this fails as browsers tend to optimize them
1632 // away. But fortunately, a <pre> tag containing a newline character
1633 // appears to work for all browsers (a would also work, but then
1634 // copying from the browser window would insert superfluous spaces into
1636 this.insertBlankLine(yIdx);
1638 line = console.childNodes[yIdx];
1640 // If necessary, promote blank '\n' line to a <div> tag
1641 if (line.tagName != 'DIV') {
1642 var div = document.createElement('div');
1643 div.style.height = this.cursorHeight + 'px';
1644 div.innerHTML = '<span></span>';
1645 console.replaceChild(div, line);
1649 // Scan through list of <span>'s until we find the one where our text
1651 span = line.firstChild;
1653 while (span.nextSibling && xPos < x) {
1654 len = this.getTextContent(span).length;
1655 if (xPos + len > x) {
1659 span = span.nextSibling;
1663 // If current <span> is not long enough, pad with spaces or add new
1665 s = this.getTextContent(span);
1666 var oldColor = span.className;
1667 var oldStyle = span.style.cssText;
1668 if (xPos + s.length < x) {
1669 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1670 span = document.createElement('span');
1671 line.appendChild(span);
1672 span.className = 'ansi0 bgAnsi15';
1673 span.style.cssText = '';
1674 oldColor = 'ansi0 bgAnsi15';
1681 } while (xPos + s.length < x);
1684 // If styles do not match, create a new <span>
1685 var del = text.length - s.length + x - xPos;
1686 if (oldColor != color ||
1687 (oldStyle != style && (oldStyle || style))) {
1689 // Replacing text at beginning of existing <span>
1690 if (text.length >= s.length) {
1691 // New text is equal or longer than existing text
1694 // Insert new <span> before the current one, then remove leading
1695 // part of existing <span>, adjust style of new <span>, and finally
1697 sibling = document.createElement('span');
1698 line.insertBefore(sibling, span);
1699 this.setTextContent(span, s.substr(text.length));
1704 // Replacing text some way into the existing <span>
1705 var remainder = s.substr(x + text.length - xPos);
1706 this.setTextContent(span, s.substr(0, x - xPos));
1708 sibling = document.createElement('span');
1709 if (span.nextSibling) {
1710 line.insertBefore(sibling, span.nextSibling);
1712 if (remainder.length) {
1713 sibling = document.createElement('span');
1714 sibling.className = oldColor;
1715 sibling.style.cssText = oldStyle;
1716 this.setTextContent(sibling, remainder);
1717 line.insertBefore(sibling, span.nextSibling);
1720 line.appendChild(sibling);
1722 if (remainder.length) {
1723 sibling = document.createElement('span');
1724 sibling.className = oldColor;
1725 sibling.style.cssText = oldStyle;
1726 this.setTextContent(sibling, remainder);
1727 line.appendChild(sibling);
1732 span.className = color;
1733 span.style.cssText = style;
1735 // Overwrite (partial) <span> with new text
1736 s = s.substr(0, x - xPos) +
1738 s.substr(x + text.length - xPos);
1740 this.setTextContent(span, s);
1743 // Delete all subsequent <span>'s that have just been overwritten
1744 sibling = span.nextSibling;
1745 while (del > 0 && sibling) {
1746 s = this.getTextContent(sibling);
1749 line.removeChild(sibling);
1751 sibling = span.nextSibling;
1753 this.setTextContent(sibling, s.substr(del));
1758 // Merge <span> with next sibling, if styles are identical
1759 if (sibling && span.className == sibling.className &&
1760 span.style.cssText == sibling.style.cssText) {
1761 this.setTextContent(span,
1762 this.getTextContent(span) +
1763 this.getTextContent(sibling));
1764 line.removeChild(sibling);
1770 this.cursorX = x + text.length;
1771 if (this.cursorX >= this.terminalWidth) {
1772 this.cursorX = this.terminalWidth - 1;
1773 if (this.cursorX < 0) {
1779 if (!this.cursor.style.visibility) {
1780 var idx = this.cursorX - xPos;
1782 // If we are in a non-empty line, take the cursor Y position from the
1783 // other elements in this line. If dealing with broken, non-proportional
1784 // fonts, this is likely to yield better results.
1785 pixelY = span.offsetTop +
1786 span.offsetParent.offsetTop;
1787 s = this.getTextContent(span);
1788 var nxtIdx = idx - s.length;
1790 this.setTextContent(this.cursor, s.charAt(idx));
1791 pixelX = span.offsetLeft +
1792 idx*span.offsetWidth / s.length;
1795 pixelX = span.offsetLeft + span.offsetWidth;
1797 if (span.nextSibling) {
1798 s = this.getTextContent(span.nextSibling);
1799 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1801 pixelX = span.nextSibling.offsetLeft +
1802 nxtIdx*span.offsetWidth / s.length;
1805 this.setTextContent(this.cursor, ' ');
1809 this.setTextContent(this.cursor, ' ');
1813 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
1816 this.setTextContent(this.space, this.spaces(this.cursorX));
1817 this.cursor.style.left = (this.space.offsetWidth +
1818 console.offsetLeft)/this.scale + 'px';
1820 this.cursorY = yIdx - this.numScrollbackLines;
1822 this.cursor.style.top = pixelY + 'px';
1824 this.cursor.style.top = yIdx*this.cursorHeight +
1825 console.offsetTop + 'px';
1829 // Merge <span> with previous sibling, if styles are identical
1830 if ((sibling = span.previousSibling) &&
1831 span.className == sibling.className &&
1832 span.style.cssText == sibling.style.cssText) {
1833 this.setTextContent(span,
1834 this.getTextContent(sibling) +
1835 this.getTextContent(span));
1836 line.removeChild(sibling);
1839 // Prune white space from the end of the current line
1840 span = line.lastChild;
1842 span.className == 'ansi0 bgAnsi15' &&
1843 !span.style.cssText.length) {
1844 // Scan backwards looking for first non-space character
1845 s = this.getTextContent(span);
1846 for (var i = s.length; i--; ) {
1847 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1848 if (i+1 != s.length) {
1849 this.setTextContent(s.substr(0, i+1));
1857 span = span.previousSibling;
1859 // Remove blank <span>'s from end of line
1860 line.removeChild(sibling);
1862 // Remove entire line (i.e. <div>), if empty
1863 var blank = document.createElement('pre');
1864 blank.style.height = this.cursorHeight + 'px';
1865 this.setTextContent(blank, '\n');
1866 line.parentNode.replaceChild(blank, line);
1873 VT100.prototype.gotoXY = function(x, y) {
1874 if (x >= this.terminalWidth) {
1875 x = this.terminalWidth - 1;
1881 if (this.offsetMode) {
1886 maxY = this.terminalHeight;
1894 this.putString(x, y, '', undefined);
1895 this.needWrap = false;
1898 VT100.prototype.gotoXaY = function(x, y) {
1899 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1902 VT100.prototype.refreshInvertedState = function() {
1903 if (this.isInverted) {
1904 this.scrollable.className += ' inverted';
1906 this.scrollable.className = this.scrollable.className.
1907 replace(/ *inverted/, '');
1911 VT100.prototype.enableAlternateScreen = function(state) {
1912 // Don't do anything, if we are already on the desired screen
1913 if ((state ? 1 : 0) == this.currentScreen) {
1914 // Calling the resizer is not actually necessary. But it is a good way
1915 // of resetting state that might have gotten corrupted.
1920 // We save the full state of the normal screen, when we switch away from it.
1921 // But for the alternate screen, no saving is necessary. We always reset
1922 // it when we switch to it.
1927 // Display new screen, and initialize state (the resizer does that for us).
1928 this.currentScreen = state ? 1 : 0;
1929 this.console[1-this.currentScreen].style.display = 'none';
1930 this.console[this.currentScreen].style.display = '';
1932 // Select appropriate character pitch.
1933 var transform = this.getTransformName();
1936 // Upon enabling the alternate screen, we switch to 80 column mode. But
1937 // upon returning to the regular screen, we restore the mode that was
1938 // in effect previously.
1939 this.console[1].style[transform] = '';
1942 this.console[this.currentScreen].style[transform];
1943 this.cursor.style[transform] = style;
1944 this.space.style[transform] = style;
1945 this.scale = style == '' ? 1.0:1.65;
1946 if (transform == 'filter') {
1947 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
1952 // If we switched to the alternate screen, reset it completely. Otherwise,
1953 // restore the saved state.
1956 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1958 this.restoreCursor();
1962 VT100.prototype.hideCursor = function() {
1963 var hidden = this.cursor.style.visibility == 'hidden';
1965 this.cursor.style.visibility = 'hidden';
1971 VT100.prototype.showCursor = function(x, y) {
1972 if (this.cursor.style.visibility) {
1973 this.cursor.style.visibility = '';
1974 this.putString(x == undefined ? this.cursorX : x,
1975 y == undefined ? this.cursorY : y,
1982 VT100.prototype.scrollBack = function() {
1983 var i = this.scrollable.scrollTop -
1984 this.scrollable.clientHeight;
1985 this.scrollable.scrollTop = i < 0 ? 0 : i;
1988 VT100.prototype.scrollFore = function() {
1989 var i = this.scrollable.scrollTop +
1990 this.scrollable.clientHeight;
1991 this.scrollable.scrollTop = i > this.numScrollbackLines *
1992 this.cursorHeight + 1
1993 ? this.numScrollbackLines *
1994 this.cursorHeight + 1
1998 VT100.prototype.spaces = function(i) {
2006 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2011 if (w > this.terminalWidth) {
2012 w = this.terminalWidth;
2014 if ((w -= x) <= 0) {
2021 if (h > this.terminalHeight) {
2022 h = this.terminalHeight;
2024 if ((h -= y) <= 0) {
2028 // Special case the situation where we clear the entire screen, and we do
2029 // not have a scrollback buffer. In that case, we should just remove all
2031 if (!this.numScrollbackLines &&
2032 w == this.terminalWidth && h == this.terminalHeight &&
2033 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2034 var console = this.console[this.currentScreen];
2035 while (console.lastChild) {
2036 console.removeChild(console.lastChild);
2038 this.putString(this.cursorX, this.cursorY, '', undefined);
2040 var hidden = this.hideCursor();
2041 var cx = this.cursorX;
2042 var cy = this.cursorY;
2043 var s = this.spaces(w);
2044 for (var i = y+h; i-- > y; ) {
2045 this.putString(x, i, s, color, style);
2047 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2051 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2053 var className = [ ];
2055 var console = this.console[this.currentScreen];
2056 if (sY >= console.childNodes.length) {
2057 text[0] = this.spaces(w);
2058 className[0] = undefined;
2059 style[0] = undefined;
2061 var line = console.childNodes[sY];
2062 if (line.tagName != 'DIV' || !line.childNodes.length) {
2063 text[0] = this.spaces(w);
2064 className[0] = undefined;
2065 style[0] = undefined;
2068 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2069 var s = this.getTextContent(span);
2072 var o = sX > x ? sX - x : 0;
2073 text[text.length] = s.substr(o, w);
2074 className[className.length] = span.className;
2075 style[style.length] = span.style.cssText;
2081 text[text.length] = this.spaces(w);
2082 className[className.length] = undefined;
2083 style[style.length] = undefined;
2087 var hidden = this.hideCursor();
2088 var cx = this.cursorX;
2089 var cy = this.cursorY;
2090 for (var i = 0; i < text.length; i++) {
2093 color = className[i];
2095 color = 'ansi0 bgAnsi15';
2097 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2098 dX += text[i].length;
2100 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2103 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2105 var left = incX < 0 ? -incX : 0;
2106 var right = incX > 0 ? incX : 0;
2107 var up = incY < 0 ? -incY : 0;
2108 var down = incY > 0 ? incY : 0;
2110 // Clip region against terminal size
2111 var dontScroll = null;
2116 if (w > this.terminalWidth - right) {
2117 w = this.terminalWidth - right;
2119 if ((w -= x) <= 0) {
2126 if (h > this.terminalHeight - down) {
2127 h = this.terminalHeight - down;
2133 if (style && style.indexOf('underline')) {
2134 // Different terminal emulators disagree on the attributes that
2135 // are used for scrolling. The consensus seems to be, never to
2136 // fill with underlined spaces. N.B. this is different from the
2137 // cases when the user blanks a region. User-initiated blanking
2138 // always fills with all of the current attributes.
2139 style = style.replace(/text-decoration:underline;/, '');
2142 // Compute current scroll position
2143 var scrollPos = this.numScrollbackLines -
2144 (this.scrollable.scrollTop-1) / this.cursorHeight;
2146 // Determine original cursor position. Hide cursor temporarily to avoid
2147 // visual artifacts.
2148 var hidden = this.hideCursor();
2149 var cx = this.cursorX;
2150 var cy = this.cursorY;
2151 var console = this.console[this.currentScreen];
2153 if (!incX && !x && w == this.terminalWidth) {
2154 // Scrolling entire lines
2157 if (!this.currentScreen && y == -incY &&
2158 h == this.terminalHeight + incY) {
2159 // Scrolling up with adding to the scrollback buffer. This is only
2160 // possible if there are at least as many lines in the console,
2161 // as the terminal is high
2162 while (console.childNodes.length < this.terminalHeight) {
2163 this.insertBlankLine(this.terminalHeight);
2166 // Add new lines at bottom in order to force scrolling
2167 for (var i = 0; i < y; i++) {
2168 this.insertBlankLine(console.childNodes.length, color, style);
2171 // Adjust the number of lines in the scrollback buffer by
2172 // removing excess entries.
2173 this.updateNumScrollbackLines();
2174 while (this.numScrollbackLines >
2175 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2176 console.removeChild(console.firstChild);
2177 this.numScrollbackLines--;
2180 // Mark lines in the scrollback buffer, so that they do not get
2182 for (var i = this.numScrollbackLines, j = -incY;
2183 i-- > 0 && j-- > 0; ) {
2184 console.childNodes[i].className = 'scrollback';
2187 // Scrolling up without adding to the scrollback buffer.
2190 console.childNodes.length >
2191 this.numScrollbackLines + y + incY; ) {
2192 console.removeChild(console.childNodes[
2193 this.numScrollbackLines + y + incY]);
2196 // If we used to have a scrollback buffer, then we must make sure
2197 // that we add back blank lines at the bottom of the terminal.
2198 // Similarly, if we are scrolling in the middle of the screen,
2199 // we must add blank lines to ensure that the bottom of the screen
2200 // does not move up.
2201 if (this.numScrollbackLines > 0 ||
2202 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2203 for (var i = -incY; i-- > 0; ) {
2204 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2213 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2214 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2216 for (var i = incY; i--; ) {
2217 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2221 // Scrolling partial lines
2223 // Scrolling up or horizontally within a line
2224 for (var i = y + this.numScrollbackLines;
2225 i < y + this.numScrollbackLines + h;
2227 this.copyLineSegment(x + incX, i + incY, x, i, w);
2231 for (var i = y + this.numScrollbackLines + h;
2232 i-- > y + this.numScrollbackLines; ) {
2233 this.copyLineSegment(x + incX, i + incY, x, i, w);
2237 // Clear blank regions
2239 this.clearRegion(x, y, incX, h, color, style);
2240 } else if (incX < 0) {
2241 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2244 this.clearRegion(x, y, w, incY, color, style);
2245 } else if (incY < 0) {
2246 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2250 // Reset scroll position
2251 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2252 this.cursorHeight + 1;
2254 // Move cursor back to its original position
2255 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2259 VT100.prototype.copy = function(selection) {
2260 if (selection == undefined) {
2261 selection = this.selection();
2263 this.internalClipboard = undefined;
2264 if (selection.length) {
2267 this.cliphelper.value = selection;
2268 this.cliphelper.select();
2269 this.cliphelper.createTextRange().execCommand('copy');
2271 this.internalClipboard = selection;
2273 this.cliphelper.value = '';
2277 VT100.prototype.copyLast = function() {
2278 // Opening the context menu can remove the selection. We try to prevent this
2279 // from happening, but that is not possible for all browsers. So, instead,
2280 // we compute the selection before showing the menu.
2281 this.copy(this.lastSelection);
2284 VT100.prototype.pasteFnc = function() {
2285 var clipboard = undefined;
2286 if (this.internalClipboard != undefined) {
2287 clipboard = this.internalClipboard;
2290 this.cliphelper.value = '';
2291 this.cliphelper.createTextRange().execCommand('paste');
2292 clipboard = this.cliphelper.value;
2296 this.cliphelper.value = '';
2297 if (clipboard && this.menu.style.visibility == 'hidden') {
2299 this.keysPressed('' + clipboard);
2306 VT100.prototype.toggleUTF = function() {
2307 this.utfEnabled = !this.utfEnabled;
2309 // We always persist the last value that the user selected. Not necessarily
2310 // the last value that a random program requested.
2311 this.utfPreferred = this.utfEnabled;
2314 VT100.prototype.toggleBell = function() {
2315 this.visualBell = !this.visualBell;
2318 VT100.prototype.toggleSoftKeyboard = function() {
2319 this.softKeyboard = !this.softKeyboard;
2320 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2323 VT100.prototype.deselectKeys = function(elem) {
2324 if (elem && elem.className == 'selected') {
2325 elem.className = '';
2327 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2328 this.deselectKeys(elem);
2332 VT100.prototype.showSoftKeyboard = function() {
2333 // Make sure no key is currently selected
2334 this.lastSelectedKey = undefined;
2335 this.deselectKeys(this.keyboard);
2336 this.isShift = false;
2337 this.showShiftState(false);
2338 this.isCtrl = false;
2339 this.showCtrlState(false);
2341 this.showAltState(false);
2343 this.keyboard.style.left = '0px';
2344 this.keyboard.style.top = '0px';
2345 this.keyboard.style.width = this.container.offsetWidth + 'px';
2346 this.keyboard.style.height = this.container.offsetHeight + 'px';
2347 this.keyboard.style.visibility = 'hidden';
2348 this.keyboard.style.display = '';
2350 var kbd = this.keyboard.firstChild;
2352 var transform = this.getTransformName();
2354 kbd.style[transform] = '';
2355 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2356 scale = (kbd.offsetWidth/
2357 this.container.offsetWidth)/0.9;
2359 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2360 scale = Math.max((kbd.offsetHeight/
2361 this.container.offsetHeight)/0.9);
2363 var style = this.getTransformStyle(transform,
2364 scale > 1.0 ? scale : undefined);
2365 kbd.style[transform] = style;
2367 if (transform == 'filter') {
2370 kbd.style.left = ((this.container.offsetWidth -
2371 kbd.offsetWidth/scale)/2) + 'px';
2372 kbd.style.top = ((this.container.offsetHeight -
2373 kbd.offsetHeight/scale)/2) + 'px';
2375 this.keyboard.style.visibility = 'visible';
2378 VT100.prototype.hideSoftKeyboard = function() {
2379 this.keyboard.style.display = 'none';
2382 VT100.prototype.toggleCursorBlinking = function() {
2383 this.blinkingCursor = !this.blinkingCursor;
2386 VT100.prototype.about = function() {
2387 alert("VT100 Terminal Emulator " + VERSION +
2388 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2389 "For more information check http://shellinabox.com");
2392 VT100.prototype.hideContextMenu = function() {
2393 this.menu.style.visibility = 'hidden';
2394 this.menu.style.top = '-100px';
2395 this.menu.style.left = '-100px';
2396 this.menu.style.width = '0px';
2397 this.menu.style.height = '0px';
2400 VT100.prototype.extendContextMenu = function(entries, actions) {
2403 VT100.prototype.showContextMenu = function(x, y) {
2404 this.menu.innerHTML =
2405 '<table class="popup" ' +
2406 'cellpadding="0" cellspacing="0">' +
2408 '<ul id="menuentries">' +
2409 '<li id="beginclipboard">Copy</li>' +
2410 '<li id="endclipboard">Paste</li>' +
2412 '<li id="reset">Reset</li>' +
2414 '<li id="beginconfig">' +
2415 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2418 (this.visualBell ? '<img src="enabled.gif" />' : '') +
2421 (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2422 'Onscreen Keyboard</li>' +
2423 '<li id="endconfig">' +
2424 (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2425 'Blinking Cursor</li>'+
2426 (this.usercss.firstChild ?
2427 '<hr id="beginusercss" />' +
2428 this.usercss.innerHTML +
2429 '<hr id="endusercss" />' :
2431 '<li id="about">About...</li>' +
2436 var popup = this.menu.firstChild;
2437 var menuentries = this.getChildById(popup, 'menuentries');
2439 // Determine menu entries that should be disabled
2440 this.lastSelection = this.selection();
2441 if (!this.lastSelection.length) {
2442 menuentries.firstChild.className
2445 var p = this.pasteFnc();
2447 menuentries.childNodes[1].className
2451 // Actions for default items
2452 var actions = [ this.copyLast, p, this.reset,
2453 this.toggleUTF, this.toggleBell,
2454 this.toggleSoftKeyboard,
2455 this.toggleCursorBlinking ];
2457 // Actions for user CSS styles (if any)
2458 for (var i = 0; i < this.usercssActions.length; ++i) {
2459 actions[actions.length] = this.usercssActions[i];
2461 actions[actions.length] = this.about;
2463 // Allow subclasses to dynamically add entries to the context menu
2464 this.extendContextMenu(menuentries, actions);
2466 // Hook up event listeners
2467 for (var node = menuentries.firstChild, i = 0; node;
2468 node = node.nextSibling) {
2469 if (node.tagName == 'LI') {
2470 if (node.className != 'disabled') {
2471 this.addListener(node, 'mouseover',
2472 function(vt100, node) {
2474 node.className = 'hover';
2477 this.addListener(node, 'mouseout',
2478 function(vt100, node) {
2480 node.className = '';
2483 this.addListener(node, 'mousedown',
2484 function(vt100, action) {
2485 return function(event) {
2486 vt100.hideContextMenu();
2488 vt100.storeUserSettings();
2489 return vt100.cancelEvent(event || window.event);
2491 }(this, actions[i]));
2492 this.addListener(node, 'mouseup',
2494 return function(event) {
2495 return vt100.cancelEvent(event || window.event);
2498 this.addListener(node, 'mouseclick',
2500 return function(event) {
2501 return vt100.cancelEvent(event || window.event);
2509 // Position menu next to the mouse pointer
2510 this.menu.style.left = '0px';
2511 this.menu.style.top = '0px';
2512 this.menu.style.width = this.container.offsetWidth + 'px';
2513 this.menu.style.height = this.container.offsetHeight + 'px';
2514 popup.style.left = '0px';
2515 popup.style.top = '0px';
2518 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2519 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2524 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2525 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2530 popup.style.left = x + 'px';
2531 popup.style.top = y + 'px';
2533 // Block all other interactions with the terminal emulator
2534 this.addListener(this.menu, 'click', function(vt100) {
2536 vt100.hideContextMenu();
2541 this.menu.style.visibility = '';
2544 VT100.prototype.keysPressed = function(ch) {
2545 for (var i = 0; i < ch.length; i++) {
2546 var c = ch.charCodeAt(i);
2547 this.vt100(c >= 7 && c <= 15 ||
2548 c == 24 || c == 26 || c == 27 || c >= 32
2549 ? String.fromCharCode(c) : '<' + c + '>');
2553 VT100.prototype.applyModifiers = function(ch, event) {
2555 if (event.ctrlKey) {
2556 if (ch >= 32 && ch <= 127) {
2557 // For historic reasons, some control characters are treated specially
2559 case /* 3 */ 51: ch = 27; break;
2560 case /* 4 */ 52: ch = 28; break;
2561 case /* 5 */ 53: ch = 29; break;
2562 case /* 6 */ 54: ch = 30; break;
2563 case /* 7 */ 55: ch = 31; break;
2564 case /* 8 */ 56: ch = 127; break;
2565 case /* ? */ 63: ch = 127; break;
2566 default: ch &= 31; break;
2570 return String.fromCharCode(ch);
2576 VT100.prototype.handleKey = function(event) {
2577 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2578 // (event.shiftKey || event.ctrlKey || event.altKey ||
2579 // event.metaKey ? ', ' +
2580 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2581 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2584 if (typeof event.charCode != 'undefined') {
2585 // non-IE keypress events have a translated charCode value. Also, our
2586 // fake events generated when receiving keydown events include this data
2588 ch = event.charCode;
2589 key = event.keyCode;
2591 // When sending a keypress event, IE includes the translated character
2592 // code in the keyCode field.
2597 // Apply modifier keys (ctrl and shift)
2601 ch = this.applyModifiers(ch, event);
2603 // By this point, "ch" is either defined and contains the character code, or
2604 // it is undefined and "key" defines the code of a function key
2605 if (ch != undefined) {
2606 this.scrollable.scrollTop = this.numScrollbackLines *
2607 this.cursorHeight + 1;
2609 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2610 // Many programs have difficulties dealing with parametrized escape
2611 // sequences for function keys. Thus, if ALT is the only modifier
2612 // key, return Emacs-style keycodes for commonly used keys.
2614 case 33: /* Page Up */ ch = '\u001B<'; break;
2615 case 34: /* Page Down */ ch = '\u001B>'; break;
2616 case 37: /* Left */ ch = '\u001Bb'; break;
2617 case 38: /* Up */ ch = '\u001Bp'; break;
2618 case 39: /* Right */ ch = '\u001Bf'; break;
2619 case 40: /* Down */ ch = '\u001Bn'; break;
2620 case 46: /* Delete */ ch = '\u001Bd'; break;
2623 } else if (event.shiftKey && !event.ctrlKey &&
2624 !event.altKey && !event.metaKey) {
2626 case 33: /* Page Up */ this.scrollBack(); return;
2627 case 34: /* Page Down */ this.scrollFore(); return;
2631 if (ch == undefined) {
2633 case 8: /* Backspace */ ch = '\u007f'; break;
2634 case 9: /* Tab */ ch = '\u0009'; break;
2635 case 10: /* Return */ ch = '\u000A'; break;
2636 case 13: /* Enter */ ch = this.crLfMode ?
2637 '\r\n' : '\r'; break;
2638 case 16: /* Shift */ return;
2639 case 17: /* Ctrl */ return;
2640 case 18: /* Alt */ return;
2641 case 19: /* Break */ return;
2642 case 20: /* Caps Lock */ return;
2643 case 27: /* Escape */ ch = '\u001B'; break;
2644 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2645 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2646 case 35: /* End */ ch = '\u001BOF'; break;
2647 case 36: /* Home */ ch = '\u001BOH'; break;
2648 case 37: /* Left */ ch = this.cursorKeyMode ?
2649 '\u001BOD' : '\u001B[D'; break;
2650 case 38: /* Up */ ch = this.cursorKeyMode ?
2651 '\u001BOA' : '\u001B[A'; break;
2652 case 39: /* Right */ ch = this.cursorKeyMode ?
2653 '\u001BOC' : '\u001B[C'; break;
2654 case 40: /* Down */ ch = this.cursorKeyMode ?
2655 '\u001BOB' : '\u001B[B'; break;
2656 case 45: /* Insert */ ch = '\u001B[2~'; break;
2657 case 46: /* Delete */ ch = '\u001B[3~'; break;
2658 case 91: /* Left Window */ return;
2659 case 92: /* Right Window */ return;
2660 case 93: /* Select */ return;
2661 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2662 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2663 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2664 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2665 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2666 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2667 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2668 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2669 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2670 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2671 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2672 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2673 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2674 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2675 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2676 case 112: /* F1 */ ch = '\u001BOP'; break;
2677 case 113: /* F2 */ ch = '\u001BOQ'; break;
2678 case 114: /* F3 */ ch = '\u001BOR'; break;
2679 case 115: /* F4 */ ch = '\u001BOS'; break;
2680 case 116: /* F5 */ ch = '\u001B[15~'; break;
2681 case 117: /* F6 */ ch = '\u001B[17~'; break;
2682 case 118: /* F7 */ ch = '\u001B[18~'; break;
2683 case 119: /* F8 */ ch = '\u001B[19~'; break;
2684 case 120: /* F9 */ ch = '\u001B[20~'; break;
2685 case 121: /* F10 */ ch = '\u001B[21~'; break;
2686 case 122: /* F11 */ ch = '\u001B[23~'; break;
2687 case 123: /* F12 */ ch = '\u001B[24~'; break;
2688 case 144: /* Num Lock */ return;
2689 case 145: /* Scroll Lock */ return;
2690 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2691 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2692 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2693 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2694 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2695 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2696 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2697 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2698 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2699 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2700 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2703 this.scrollable.scrollTop = this.numScrollbackLines *
2704 this.cursorHeight + 1;
2708 // "ch" now contains the sequence of keycodes to send. But we might still
2709 // have to apply the effects of modifier keys.
2710 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2711 var start, digit, part1, part2;
2712 if ((start = ch.substr(0, 2)) == '\u001B[') {
2714 part1.length < ch.length &&
2715 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2716 part1 = ch.substr(0, part1.length + 1);
2718 part2 = ch.substr(part1.length);
2719 if (part1.length > 2) {
2722 } else if (start == '\u001BO') {
2724 part2 = ch.substr(2);
2726 if (part1 != undefined) {
2728 ((event.shiftKey ? 1 : 0) +
2729 (event.altKey|event.metaKey ? 2 : 0) +
2730 (event.ctrlKey ? 4 : 0)) +
2732 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2737 if (this.menu.style.visibility == 'hidden') {
2738 // this.vt100('R: c=');
2739 // for (var i = 0; i < ch.length; i++)
2740 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2741 // this.vt100('\r\n');
2742 this.keysPressed(ch);
2746 VT100.prototype.inspect = function(o, d) {
2747 if (d == undefined) {
2751 if (typeof o == 'object' && ++d < 2) {
2754 rc += this.spaces(d * 2) + i + ' -> ';
2756 rc += this.inspect(o[i], d);
2758 rc += '?' + '?' + '?\r\n';
2763 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2768 VT100.prototype.checkComposedKeys = function(event) {
2769 // Composed keys (at least on Linux) do not generate normal events.
2770 // Instead, they get entered into the text field. We normally catch
2771 // this on the next keyup event.
2772 var s = this.input.value;
2774 this.input.value = '';
2775 if (this.menu.style.visibility == 'hidden') {
2776 this.keysPressed(s);
2781 VT100.prototype.fixEvent = function(event) {
2782 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2783 // is used as a second-level selector, clear the modifier bits before
2784 // handling the event.
2785 if (event.ctrlKey && event.altKey) {
2787 fake.charCode = event.charCode;
2788 fake.keyCode = event.keyCode;
2789 fake.ctrlKey = false;
2790 fake.shiftKey = event.shiftKey;
2791 fake.altKey = false;
2792 fake.metaKey = event.metaKey;
2796 // Some browsers fail to translate keys, if both shift and alt/meta is
2797 // pressed at the same time. We try to translate those cases, but that
2798 // only works for US keyboard layouts.
2799 if (event.shiftKey) {
2802 switch (this.lastNormalKeyDownEvent.keyCode) {
2803 case 39: /* ' -> " */ u = 39; s = 34; break;
2804 case 44: /* , -> < */ u = 44; s = 60; break;
2805 case 45: /* - -> _ */ u = 45; s = 95; break;
2806 case 46: /* . -> > */ u = 46; s = 62; break;
2807 case 47: /* / -> ? */ u = 47; s = 63; break;
2809 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2810 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2811 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2812 case 51: /* 3 -> # */ u = 51; s = 35; break;
2813 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2814 case 53: /* 5 -> % */ u = 53; s = 37; break;
2815 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2816 case 55: /* 7 -> & */ u = 55; s = 38; break;
2817 case 56: /* 8 -> * */ u = 56; s = 42; break;
2818 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2820 case 59: /* ; -> : */ u = 59; s = 58; break;
2821 case 61: /* = -> + */ u = 61; s = 43; break;
2822 case 91: /* [ -> { */ u = 91; s = 123; break;
2823 case 92: /* \ -> | */ u = 92; s = 124; break;
2824 case 93: /* ] -> } */ u = 93; s = 125; break;
2825 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2827 case 109: /* - -> _ */ u = 45; s = 95; break;
2828 case 111: /* / -> ? */ u = 47; s = 63; break;
2830 case 186: /* ; -> : */ u = 59; s = 58; break;
2831 case 187: /* = -> + */ u = 61; s = 43; break;
2832 case 188: /* , -> < */ u = 44; s = 60; break;
2833 case 189: /* - -> _ */ u = 45; s = 95; break;
2834 case 190: /* . -> > */ u = 46; s = 62; break;
2835 case 191: /* / -> ? */ u = 47; s = 63; break;
2836 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2837 case 219: /* [ -> { */ u = 91; s = 123; break;
2838 case 220: /* \ -> | */ u = 92; s = 124; break;
2839 case 221: /* ] -> } */ u = 93; s = 125; break;
2840 case 222: /* ' -> " */ u = 39; s = 34; break;
2843 if (s && (event.charCode == u || event.charCode == 0)) {
2846 fake.keyCode = event.keyCode;
2847 fake.ctrlKey = event.ctrlKey;
2848 fake.shiftKey = event.shiftKey;
2849 fake.altKey = event.altKey;
2850 fake.metaKey = event.metaKey;
2857 VT100.prototype.keyDown = function(event) {
2858 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2859 // (event.shiftKey || event.ctrlKey || event.altKey ||
2860 // event.metaKey ? ', ' +
2861 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2862 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2864 this.checkComposedKeys(event);
2865 this.lastKeyPressedEvent = undefined;
2866 this.lastKeyDownEvent = undefined;
2867 this.lastNormalKeyDownEvent = event;
2870 event.keyCode == 32 ||
2871 event.keyCode >= 48 && event.keyCode <= 57 ||
2872 event.keyCode >= 65 && event.keyCode <= 90;
2875 event.keyCode >= 96 && event.keyCode <= 105 ||
2876 event.keyCode == 226;
2879 event.keyCode == 59 || event.keyCode == 61 ||
2880 event.keyCode == 106 || event.keyCode == 107 ||
2881 event.keyCode >= 109 && event.keyCode <= 111 ||
2882 event.keyCode >= 186 && event.keyCode <= 192 ||
2883 event.keyCode >= 219 && event.keyCode <= 223 ||
2884 event.keyCode == 252;
2886 if (navigator.appName == 'Konqueror') {
2887 normalKey |= event.keyCode < 128;
2892 // We normally prefer to look at keypress events, as they perform the
2893 // translation from keyCode to charCode. This is important, as the
2894 // translation is locale-dependent.
2895 // But for some keys, we must intercept them during the keydown event,
2896 // as they would otherwise get interpreted by the browser.
2897 // Even, when doing all of this, there are some keys that we can never
2898 // intercept. This applies to some of the menu navigation keys in IE.
2899 // In fact, we see them, but we cannot stop IE from seeing them, too.
2900 if ((event.charCode || event.keyCode) &&
2901 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2903 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2904 // interpret this sequence ourselves, as some keyboard layouts use
2905 // it for second-level layouts.
2906 !(event.ctrlKey && event.altKey)) ||
2907 this.catchModifiersEarly && normalKey && !alphNumKey &&
2908 (event.ctrlKey || event.altKey || event.metaKey) ||
2910 this.lastKeyDownEvent = event;
2912 fake.ctrlKey = event.ctrlKey;
2913 fake.shiftKey = event.shiftKey;
2914 fake.altKey = event.altKey;
2915 fake.metaKey = event.metaKey;
2917 fake.charCode = event.keyCode;
2921 fake.keyCode = event.keyCode;
2922 if (!alphNumKey && event.shiftKey) {
2923 fake = this.fixEvent(fake);
2927 this.handleKey(fake);
2928 this.lastNormalKeyDownEvent = undefined;
2931 // For non-IE browsers
2932 event.stopPropagation();
2933 event.preventDefault();
2938 event.cancelBubble = true;
2939 event.returnValue = false;
2949 VT100.prototype.keyPressed = function(event) {
2950 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2951 // (event.shiftKey || event.ctrlKey || event.altKey ||
2952 // event.metaKey ? ', ' +
2953 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2954 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2956 if (this.lastKeyDownEvent) {
2957 // If we already processed the key on keydown, do not process it
2958 // again here. Ideally, the browser should not even have generated a
2959 // keypress event in this case. But that does not appear to always work.
2960 this.lastKeyDownEvent = undefined;
2962 this.handleKey(event.altKey || event.metaKey
2963 ? this.fixEvent(event) : event);
2967 // For non-IE browsers
2968 event.preventDefault();
2974 event.cancelBubble = true;
2975 event.returnValue = false;
2980 this.lastNormalKeyDownEvent = undefined;
2981 this.lastKeyPressedEvent = event;
2985 VT100.prototype.keyUp = function(event) {
2986 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2987 // (event.shiftKey || event.ctrlKey || event.altKey ||
2988 // event.metaKey ? ', ' +
2989 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2990 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2992 if (this.lastKeyPressedEvent) {
2993 // The compose key on Linux occasionally confuses the browser and keeps
2994 // inserting bogus characters into the input field, even if just a regular
2995 // key has been pressed. Detect this case and drop the bogus characters.
2997 event.srcElement).value = '';
2999 // This is usually were we notice that a key has been composed and
3000 // thus failed to generate normal events.
3001 this.checkComposedKeys(event);
3003 // Some browsers don't report keypress events if ctrl or alt is pressed
3004 // for non-alphanumerical keys. Patch things up for now, but in the
3005 // future we will catch these keys earlier (in the keydown handler).
3006 if (this.lastNormalKeyDownEvent) {
3007 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3008 this.catchModifiersEarly = true;
3010 event.keyCode == 32 ||
3011 event.keyCode >= 48 && event.keyCode <= 57 ||
3012 event.keyCode >= 65 && event.keyCode <= 90;
3015 event.keyCode >= 96 && event.keyCode <= 105;
3018 event.keyCode == 59 || event.keyCode == 61 ||
3019 event.keyCode == 106 || event.keyCode == 107 ||
3020 event.keyCode >= 109 && event.keyCode <= 111 ||
3021 event.keyCode >= 186 && event.keyCode <= 192 ||
3022 event.keyCode >= 219 && event.keyCode <= 223 ||
3023 event.keyCode == 252;
3025 fake.ctrlKey = event.ctrlKey;
3026 fake.shiftKey = event.shiftKey;
3027 fake.altKey = event.altKey;
3028 fake.metaKey = event.metaKey;
3030 fake.charCode = event.keyCode;
3034 fake.keyCode = event.keyCode;
3035 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3036 fake = this.fixEvent(fake);
3039 this.lastNormalKeyDownEvent = undefined;
3040 this.handleKey(fake);
3046 event.cancelBubble = true;
3047 event.returnValue = false;
3052 this.lastKeyDownEvent = undefined;
3053 this.lastKeyPressedEvent = undefined;
3057 VT100.prototype.animateCursor = function(inactive) {
3058 if (!this.cursorInterval) {
3059 this.cursorInterval = setInterval(
3062 vt100.animateCursor();
3064 // Use this opportunity to check whether the user entered a composed
3065 // key, or whether somebody pasted text into the textfield.
3066 vt100.checkComposedKeys();
3070 if (inactive != undefined || this.cursor.className != 'inactive') {
3072 this.cursor.className = 'inactive';
3074 if (this.blinkingCursor) {
3075 this.cursor.className = this.cursor.className == 'bright'
3078 this.cursor.className = 'bright';
3084 VT100.prototype.blurCursor = function() {
3085 this.animateCursor(true);
3088 VT100.prototype.focusCursor = function() {
3089 this.animateCursor(false);
3092 VT100.prototype.flashScreen = function() {
3093 this.isInverted = !this.isInverted;
3094 this.refreshInvertedState();
3095 this.isInverted = !this.isInverted;
3096 setTimeout(function(vt100) {
3098 vt100.refreshInvertedState();
3103 VT100.prototype.beep = function() {
3104 if (this.visualBell) {
3111 this.beeper.src = 'beep.wav';
3118 VT100.prototype.bs = function() {
3119 if (this.cursorX > 0) {
3120 this.gotoXY(this.cursorX - 1, this.cursorY);
3121 this.needWrap = false;
3125 VT100.prototype.ht = function(count) {
3126 if (count == undefined) {
3129 var cx = this.cursorX;
3130 while (count-- > 0) {
3131 while (cx++ < this.terminalWidth) {
3132 var tabState = this.userTabStop[cx];
3133 if (tabState == false) {
3134 // Explicitly cleared tab stop
3136 } else if (tabState) {
3137 // Explicitly set tab stop
3140 // Default tab stop at each eighth column
3147 if (cx > this.terminalWidth - 1) {
3148 cx = this.terminalWidth - 1;
3150 if (cx != this.cursorX) {
3151 this.gotoXY(cx, this.cursorY);
3155 VT100.prototype.rt = function(count) {
3156 if (count == undefined) {
3159 var cx = this.cursorX;
3160 while (count-- > 0) {
3162 var tabState = this.userTabStop[cx];
3163 if (tabState == false) {
3164 // Explicitly cleared tab stop
3166 } else if (tabState) {
3167 // Explicitly set tab stop
3170 // Default tab stop at each eighth column
3180 if (cx != this.cursorX) {
3181 this.gotoXY(cx, this.cursorY);
3185 VT100.prototype.cr = function() {
3186 this.gotoXY(0, this.cursorY);
3187 this.needWrap = false;
3190 VT100.prototype.lf = function(count) {
3191 if (count == undefined) {
3194 if (count > this.terminalHeight) {
3195 count = this.terminalHeight;
3201 while (count-- > 0) {
3202 if (this.cursorY == this.bottom - 1) {
3203 this.scrollRegion(0, this.top + 1,
3204 this.terminalWidth, this.bottom - this.top - 1,
3205 0, -1, this.color, this.style);
3207 } else if (this.cursorY < this.terminalHeight - 1) {
3208 this.gotoXY(this.cursorX, this.cursorY + 1);
3213 VT100.prototype.ri = function(count) {
3214 if (count == undefined) {
3217 if (count > this.terminalHeight) {
3218 count = this.terminalHeight;
3224 while (count-- > 0) {
3225 if (this.cursorY == this.top) {
3226 this.scrollRegion(0, this.top,
3227 this.terminalWidth, this.bottom - this.top - 1,
3228 0, 1, this.color, this.style);
3229 } else if (this.cursorY > 0) {
3230 this.gotoXY(this.cursorX, this.cursorY - 1);
3233 this.needWrap = false;
3236 VT100.prototype.respondID = function() {
3237 this.respondString += '\u001B[?6c';
3240 VT100.prototype.respondSecondaryDA = function() {
3241 this.respondString += '\u001B[>0;0;0c';
3245 VT100.prototype.updateStyle = function() {
3247 if (this.attr & ATTR_UNDERLINE) {
3248 this.style = 'text-decoration:underline;';
3250 var bg = (this.attr >> 4) & 0xF;
3251 var fg = this.attr & 0xF;
3252 if (this.attr & ATTR_REVERSE) {
3257 if ((this.attr & (ATTR_REVERSE | ATTR_DIM)) == ATTR_DIM) {
3258 fg = 8; // Dark grey
3259 } else if (this.attr & ATTR_BRIGHT) {
3262 if (this.attr & ATTR_BLINK) {
3265 // Make some readability enhancements. Most notably, disallow identical
3266 // background and foreground colors.
3268 if ((fg ^= 8) == 7) {
3272 // And disallow bright colors on a light-grey background.
3273 if (bg == 7 && fg >= 8) {
3274 if ((fg -= 8) == 7) {
3279 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3282 VT100.prototype.setAttrColors = function(attr) {
3283 if (attr != this.attr) {
3289 VT100.prototype.saveCursor = function() {
3290 this.savedX[this.currentScreen] = this.cursorX;
3291 this.savedY[this.currentScreen] = this.cursorY;
3292 this.savedAttr[this.currentScreen] = this.attr;
3293 this.savedUseGMap = this.useGMap;
3294 for (var i = 0; i < 4; i++) {
3295 this.savedGMap[i] = this.GMap[i];
3297 this.savedValid[this.currentScreen] = true;
3300 VT100.prototype.restoreCursor = function() {
3301 if (!this.savedValid[this.currentScreen]) {
3304 this.attr = this.savedAttr[this.currentScreen];
3306 this.useGMap = this.savedUseGMap;
3307 for (var i = 0; i < 4; i++) {
3308 this.GMap[i] = this.savedGMap[i];
3310 this.translate = this.GMap[this.useGMap];
3311 this.needWrap = false;
3312 this.gotoXY(this.savedX[this.currentScreen],
3313 this.savedY[this.currentScreen]);
3316 VT100.prototype.getTransformName = function() {
3317 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3318 for (var i = 0; i < styles.length; ++i) {
3319 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3326 VT100.prototype.getTransformStyle = function(transform, scale) {
3327 return scale && scale != 1.0
3328 ? transform == 'filter'
3329 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3330 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3331 "sizingMethod='auto expand')"
3332 : 'translateX(-50%) ' +
3333 'scaleX(' + (1.0/scale) + ') ' +
3338 VT100.prototype.set80_132Mode = function(state) {
3339 var transform = this.getTransformName();
3341 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3345 this.getTransformStyle(transform, 1.65):'';
3346 this.console[this.currentScreen].style[transform] = style;
3347 this.cursor.style[transform] = style;
3348 this.space.style[transform] = style;
3349 this.scale = state ? 1.65 : 1.0;
3350 if (transform == 'filter') {
3351 this.console[this.currentScreen].style.width = state ? '165%' : '';
3357 VT100.prototype.setMode = function(state) {
3358 for (var i = 0; i <= this.npar; i++) {
3359 if (this.isQuestionMark) {
3360 switch (this.par[i]) {
3361 case 1: this.cursorKeyMode = state; break;
3362 case 3: this.set80_132Mode(state); break;
3363 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3364 case 6: this.offsetMode = state; break;
3365 case 7: this.autoWrapMode = state; break;
3367 case 9: this.mouseReporting = state; break;
3368 case 25: this.cursorNeedsShowing = state;
3369 if (state) { this.showCursor(); }
3370 else { this.hideCursor(); } break;
3373 case 47: this.enableAlternateScreen(state); break;
3377 switch (this.par[i]) {
3378 case 3: this.dispCtrl = state; break;
3379 case 4: this.insertMode = state; break;
3380 case 20:this.crLfMode = state; break;
3387 VT100.prototype.statusReport = function() {
3388 // Ready and operational.
3389 this.respondString += '\u001B[0n';
3392 VT100.prototype.cursorReport = function() {
3393 this.respondString += '\u001B[' +
3394 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3396 (this.cursorX + 1) +
3400 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3401 // Changing of cursor color is not implemented.
3404 VT100.prototype.openPrinterWindow = function() {
3407 if (!this.printWin || this.printWin.closed) {
3408 this.printWin = window.open('', 'print-output',
3409 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3410 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3411 this.printWin.document.body.innerHTML =
3412 '<link rel="stylesheet" href="' +
3413 document.location.protocol + '//' + document.location.host +
3414 document.location.pathname.replace(/[^/]*$/, '') +
3415 'print-styles.css" type="text/css">\n' +
3416 '<div id="options"><input id="autoprint" type="checkbox"' +
3417 (this.autoprint ? ' checked' : '') + '>' +
3418 'Automatically, print page(s) when job is ready' +
3419 '</input></div>\n' +
3420 '<div id="spacer"><input type="checkbox"> </input></div>' +
3421 '<pre id="print"></pre>\n';
3422 var autoprint = this.printWin.document.getElementById('autoprint');
3423 this.addListener(autoprint, 'click',
3424 (function(vt100, autoprint) {
3426 vt100.autoprint = autoprint.checked;
3427 vt100.storeUserSettings();
3430 })(this, autoprint));
3431 this.printWin.document.title = 'ShellInABox Printer Output';
3434 // Maybe, a popup blocker prevented us from working. Better catch the
3435 // exception, so that we won't break the entire terminal session. The
3436 // user probably needs to disable the blocker first before retrying the
3440 rc &= this.printWin && !this.printWin.closed &&
3441 (this.printWin.innerWidth ||
3442 this.printWin.document.documentElement.clientWidth ||
3443 this.printWin.document.body.clientWidth) > 1;
3445 if (!rc && this.printing == 100) {
3446 // Different popup blockers work differently. We try to detect a couple
3447 // of common methods. And then we retry again a brief amount later, as
3448 // false positives are otherwise possible. If we are sure that there is
3449 // a popup blocker in effect, we alert the user to it. This is helpful
3450 // as some popup blockers have minimal or no UI, and the user might not
3451 // notice that they are missing the popup. In any case, we only show at
3452 // most one message per print job.
3453 this.printing = true;
3454 setTimeout((function(win) {
3456 if (!win || win.closed ||
3458 win.document.documentElement.clientWidth ||
3459 win.document.body.clientWidth) <= 1) {
3460 alert('Attempted to print, but a popup blocker ' +
3461 'prevented the printer window from opening');
3464 })(this.printWin), 2000);
3469 VT100.prototype.sendToPrinter = function(s) {
3470 this.openPrinterWindow();
3472 var doc = this.printWin.document;
3473 var print = doc.getElementById('print');
3474 if (print.lastChild && print.lastChild.nodeName == '#text') {
3475 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3477 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3480 // There probably was a more aggressive popup blocker that prevented us
3481 // from accessing the printer windows.
3485 VT100.prototype.sendControlToPrinter = function(ch) {
3486 // We get called whenever doControl() is active. But for the printer, we
3487 // only implement a basic line printer that doesn't understand most of
3488 // the escape sequences of the VT100 terminal. In fact, the only escape
3489 // sequence that we really need to recognize is '^[[5i' for turning the
3495 this.openPrinterWindow();
3496 var doc = this.printWin.document;
3497 var print = doc.getElementById('print');
3498 var chars = print.lastChild &&
3499 print.lastChild.nodeName == '#text' ?
3500 print.lastChild.textContent.length : 0;
3501 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3508 this.openPrinterWindow();
3509 var pageBreak = this.printWin.document.createElement('div');
3510 pageBreak.className = 'pagebreak';
3511 pageBreak.innerHTML = '<hr />';
3512 this.printWin.document.getElementById('print').appendChild(pageBreak);
3516 this.openPrinterWindow();
3517 var lineBreak = this.printWin.document.createElement('br');
3518 this.printWin.document.getElementById('print').appendChild(lineBreak);
3525 switch (this.isEsc) {
3527 this.isEsc = ESnormal;
3530 this.isEsc = ESsquare;
3538 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3539 0, 0, 0, 0, 0, 0, 0, 0 ];
3540 this.isEsc = ESgetpars;
3541 this.isQuestionMark = ch == 0x3F /*?*/;
3542 if (this.isQuestionMark) {
3547 if (ch == 0x3B /*;*/) {
3550 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3551 var par = this.par[this.npar];
3552 if (par == undefined) {
3555 this.par[this.npar] = 10*par + (ch & 0xF);
3558 this.isEsc = ESgotpars;
3562 this.isEsc = ESnormal;
3563 if (this.isQuestionMark) {
3568 this.csii(this.par[0]);
3575 this.isEsc = ESnormal;
3581 // There probably was a more aggressive popup blocker that prevented us
3582 // from accessing the printer windows.
3586 VT100.prototype.csiAt = function(number) {
3591 if (number > this.terminalWidth - this.cursorX) {
3592 number = this.terminalWidth - this.cursorX;
3594 this.scrollRegion(this.cursorX, this.cursorY,
3595 this.terminalWidth - this.cursorX - number, 1,
3596 number, 0, this.color, this.style);
3597 this.needWrap = false;
3600 VT100.prototype.csii = function(number) {
3603 case 0: // Print Screen
3606 case 4: // Stop printing
3608 if (this.printing && this.printWin && !this.printWin.closed) {
3609 var print = this.printWin.document.getElementById('print');
3610 while (print.lastChild &&
3611 print.lastChild.tagName == 'DIV' &&
3612 print.lastChild.className == 'pagebreak') {
3613 // Remove trailing blank pages
3614 print.removeChild(print.lastChild);
3616 if (this.autoprint) {
3617 this.printWin.print();
3622 this.printing = false;
3624 case 5: // Start printing
3625 if (!this.printing && this.printWin && !this.printWin.closed) {
3626 this.printWin.document.getElementById('print').innerHTML = '';
3628 this.printing = 100;
3635 VT100.prototype.csiJ = function(number) {
3637 case 0: // Erase from cursor to end of display
3638 this.clearRegion(this.cursorX, this.cursorY,
3639 this.terminalWidth - this.cursorX, 1,
3640 this.color, this.style);
3641 if (this.cursorY < this.terminalHeight-2) {
3642 this.clearRegion(0, this.cursorY+1,
3643 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3644 this.color, this.style);
3647 case 1: // Erase from start to cursor
3648 if (this.cursorY > 0) {
3649 this.clearRegion(0, 0,
3650 this.terminalWidth, this.cursorY,
3651 this.color, this.style);
3653 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3654 this.color, this.style);
3656 case 2: // Erase whole display
3657 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3658 this.color, this.style);
3666 VT100.prototype.csiK = function(number) {
3668 case 0: // Erase from cursor to end of line
3669 this.clearRegion(this.cursorX, this.cursorY,
3670 this.terminalWidth - this.cursorX, 1,
3671 this.color, this.style);
3673 case 1: // Erase from start of line to cursor
3674 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3675 this.color, this.style);
3677 case 2: // Erase whole line
3678 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3679 this.color, this.style);
3687 VT100.prototype.csiL = function(number) {
3688 // Open line by inserting blank line(s)
3689 if (this.cursorY >= this.bottom) {
3695 if (number > this.bottom - this.cursorY) {
3696 number = this.bottom - this.cursorY;
3698 this.scrollRegion(0, this.cursorY,
3699 this.terminalWidth, this.bottom - this.cursorY - number,
3700 0, number, this.color, this.style);
3704 VT100.prototype.csiM = function(number) {
3705 // Delete line(s), scrolling up the bottom of the screen.
3706 if (this.cursorY >= this.bottom) {
3712 if (number > this.bottom - this.cursorY) {
3713 number = bottom - cursorY;
3715 this.scrollRegion(0, this.cursorY + number,
3716 this.terminalWidth, this.bottom - this.cursorY - number,
3717 0, -number, this.color, this.style);
3721 VT100.prototype.csim = function() {
3722 for (var i = 0; i <= this.npar; i++) {
3723 switch (this.par[i]) {
3724 case 0: this.attr = ATTR_DEFAULT; break;
3725 case 1: this.attr = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT; break;
3726 case 2: this.attr = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM; break;
3727 case 4: this.attr |= ATTR_UNDERLINE; break;
3728 case 5: this.attr |= ATTR_BLINK; break;
3729 case 7: this.attr |= ATTR_REVERSE; break;
3731 this.translate = this.GMap[this.useGMap];
3732 this.dispCtrl = false;
3733 this.toggleMeta = false;
3736 this.translate = this.CodePage437Map;
3737 this.dispCtrl = true;
3738 this.toggleMeta = false;
3741 this.translate = this.CodePage437Map;
3742 this.dispCtrl = true;
3743 this.toggleMeta = true;
3746 case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM); break;
3747 case 24: this.attr &= ~ ATTR_UNDERLINE; break;
3748 case 25: this.attr &= ~ ATTR_BLINK; break;
3749 case 27: this.attr &= ~ ATTR_REVERSE; break;
3750 case 38: this.attr = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|
3751 ATTR_UNDERLINE; break;
3752 case 39: this.attr &= ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F); break;
3753 case 49: this.attr |= 0xF0; break;
3755 if (this.par[i] >= 30 && this.par[i] <= 37) {
3756 var fg = this.par[i] - 30;
3757 this.attr = (this.attr & ~0x0F) | fg;
3758 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3759 var bg = this.par[i] - 40;
3760 this.attr = (this.attr & ~0xF0) | (bg << 4);
3768 VT100.prototype.csiP = function(number) {
3769 // Delete character(s) following cursor
3773 if (number > this.terminalWidth - this.cursorX) {
3774 number = this.terminalWidth - this.cursorX;
3776 this.scrollRegion(this.cursorX + number, this.cursorY,
3777 this.terminalWidth - this.cursorX - number, 1,
3778 -number, 0, this.color, this.style);
3782 VT100.prototype.csiX = function(number) {
3783 // Clear characters following cursor
3787 if (number > this.terminalWidth - this.cursorX) {
3788 number = this.terminalWidth - this.cursorX;
3790 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3791 this.color, this.style);
3795 VT100.prototype.settermCommand = function() {
3796 // Setterm commands are not implemented
3799 VT100.prototype.doControl = function(ch) {
3800 if (this.printing) {
3801 this.sendControlToPrinter(ch);
3806 case 0x00: /* ignored */ break;
3807 case 0x08: this.bs(); break;
3808 case 0x09: this.ht(); break;
3812 case 0x84: this.lf(); if (!this.crLfMode) break;
3813 case 0x0D: this.cr(); break;
3814 case 0x85: this.cr(); this.lf(); break;
3815 case 0x0E: this.useGMap = 1;
3816 this.translate = this.GMap[1];
3817 this.dispCtrl = true; break;
3818 case 0x0F: this.useGMap = 0;
3819 this.translate = this.GMap[0];
3820 this.dispCtrl = false; break;
3822 case 0x1A: this.isEsc = ESnormal; break;
3823 case 0x1B: this.isEsc = ESesc; break;
3824 case 0x7F: /* ignored */ break;
3825 case 0x88: this.userTabStop[this.cursorX] = true; break;
3826 case 0x8D: this.ri(); break;
3827 case 0x8E: this.isEsc = ESss2; break;
3828 case 0x8F: this.isEsc = ESss3; break;
3829 case 0x9A: this.respondID(); break;
3830 case 0x9B: this.isEsc = ESsquare; break;
3831 case 0x07: if (this.isEsc != ESstatus) {
3835 default: switch (this.isEsc) {
3837 this.isEsc = ESnormal;
3839 /*%*/ case 0x25: this.isEsc = ESpercent; break;
3840 /*(*/ case 0x28: this.isEsc = ESsetG0; break;
3842 /*)*/ case 0x29: this.isEsc = ESsetG1; break;
3844 /***/ case 0x2A: this.isEsc = ESsetG2; break;
3846 /*+*/ case 0x2B: this.isEsc = ESsetG3; break;
3847 /*#*/ case 0x23: this.isEsc = EShash; break;
3848 /*7*/ case 0x37: this.saveCursor(); break;
3849 /*8*/ case 0x38: this.restoreCursor(); break;
3850 /*>*/ case 0x3E: this.applKeyMode = false; break;
3851 /*=*/ case 0x3D: this.applKeyMode = true; break;
3852 /*D*/ case 0x44: this.lf(); break;
3853 /*E*/ case 0x45: this.cr(); this.lf(); break;
3854 /*M*/ case 0x4D: this.ri(); break;
3855 /*N*/ case 0x4E: this.isEsc = ESss2; break;
3856 /*O*/ case 0x4F: this.isEsc = ESss3; break;
3857 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3858 /*Z*/ case 0x5A: this.respondID(); break;
3859 /*[*/ case 0x5B: this.isEsc = ESsquare; break;
3860 /*]*/ case 0x5D: this.isEsc = ESnonstd; break;
3861 /*c*/ case 0x63: this.reset(); break;
3862 /*g*/ case 0x67: this.flashScreen(); break;
3870 /*2*/ case 0x32: this.statusString = ''; this.isEsc = ESstatus; break;
3871 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3872 this.isEsc = ESpalette; break;
3873 /*R*/ case 0x52: // Palette support is not implemented
3874 this.isEsc = ESnormal; break;
3875 default: this.isEsc = ESnormal; break;
3879 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3880 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3881 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3882 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3884 if (this.npar == 7) {
3885 // Palette support is not implemented
3886 this.isEsc = ESnormal;
3889 this.isEsc = ESnormal;
3894 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3895 0, 0, 0, 0, 0, 0, 0, 0 ];
3896 this.isEsc = ESgetpars;
3897 /*[*/ if (ch == 0x5B) { // Function key
3898 this.isEsc = ESfunckey;
3901 /*?*/ this.isQuestionMark = ch == 0x3F;
3902 if (this.isQuestionMark) {
3909 /*;*/ if (ch == 0x3B) {
3912 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3913 var par = this.par[this.npar];
3914 if (par == undefined) {
3917 this.par[this.npar] = 10*par + (ch & 0xF);
3919 } else if (this.isEsc == ESdeviceattr) {
3921 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3922 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3923 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3924 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3927 this.isEsc = ESnormal;
3930 this.isEsc = ESgotpars;
3934 this.isEsc = ESnormal;
3935 if (this.isQuestionMark) {
3937 /*h*/ case 0x68: this.setMode(true); break;
3938 /*l*/ case 0x6C: this.setMode(false); break;
3939 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3942 this.isQuestionMark = false;
3946 /*!*/ case 0x21: this.isEsc = ESbang; break;
3947 /*>*/ case 0x3E: if (!this.npar) this.isEsc = ESdeviceattr; break;
3949 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3950 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3951 this.cursorY - (this.par[0] ? this.par[0] : 1));
3954 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3955 this.cursorY + (this.par[0] ? this.par[0] : 1));
3958 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3959 this.cursorY); break;
3960 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3961 this.cursorY); break;
3962 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3964 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3966 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3968 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3969 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3970 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3971 /*i*/ case 0x69: this.csii(this.par[0]); break;
3972 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3973 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3974 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3975 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3976 /*m*/ case 0x6D: this.csim(); break;
3977 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3978 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3979 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3980 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3981 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3982 /*g*/ case 0x67: if (this.par[0] == 0) {
3983 this.userTabStop[this.cursorX] = false;
3984 } else if (this.par[0] == 2 || this.par[0] == 3) {
3985 this.userTabStop = [ ];
3986 for (var i = 0; i < this.terminalWidth; i++) {
3987 this.userTabStop[i] = false;
3991 /*h*/ case 0x68: this.setMode(true); break;
3992 /*l*/ case 0x6C: this.setMode(false); break;
3993 /*n*/ case 0x6E: switch (this.par[0]) {
3994 case 5: this.statusReport(); break;
3995 case 6: this.cursorReport(); break;
3999 /*q*/ case 0x71: // LED control not implemented
4001 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4002 var b = this.par[1] ? this.par[1]
4003 : this.terminalHeight;
4004 if (t < b && b <= this.terminalHeight) {
4010 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4011 if (c > this.terminalWidth * this.terminalHeight) {
4012 c = this.terminalWidth * this.terminalHeight;
4015 lineBuf += this.lastCharacter;
4018 /*s*/ case 0x73: this.saveCursor(); break;
4019 /*u*/ case 0x75: this.restoreCursor(); break;
4020 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4021 /*]*/ case 0x5D: this.settermCommand(); break;
4029 this.isEsc = ESnormal;
4032 this.isEsc = ESnormal;
4034 /*@*/ case 0x40: this.utfEnabled = false; break;
4036 /*8*/ case 0x38: this.utfEnabled = true; break;
4041 this.isEsc = ESnormal; break;
4043 this.isEsc = ESnormal;
4044 /*8*/ if (ch == 0x38) {
4045 // Screen alignment test not implemented
4052 var g = this.isEsc - ESsetG0;
4053 this.isEsc = ESnormal;
4055 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4057 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4058 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4059 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4062 if (this.useGMap == g) {
4063 this.translate = this.GMap[g];
4068 if (this.statusString && this.statusString.charAt(0) == ';') {
4069 this.statusString = this.statusString.substr(1);
4072 window.status = this.statusString;
4075 this.isEsc = ESnormal;
4077 this.statusString += String.fromCharCode(ch);
4083 ch = this.GMap[this.isEsc - ESss2 + 2]
4084 [this.toggleMeta ? (ch | 0x80) : ch];
4085 if ((ch & 0xFF00) == 0xF000) {
4087 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4088 this.isEsc = ESnormal; break;
4091 this.lastCharacter = String.fromCharCode(ch);
4092 lineBuf += this.lastCharacter;
4093 this.isEsc = ESnormal; break;
4095 this.isEsc = ESnormal; break;
4102 VT100.prototype.renderString = function(s, showCursor) {
4103 if (this.printing) {
4104 this.sendToPrinter(s);
4111 // We try to minimize the number of DOM operations by coalescing individual
4112 // characters into strings. This is a significant performance improvement.
4113 var incX = s.length;
4114 if (incX > this.terminalWidth - this.cursorX) {
4115 incX = this.terminalWidth - this.cursorX;
4119 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4122 // Minimize the number of calls to putString(), by avoiding a direct
4123 // call to this.showCursor()
4124 this.cursor.style.visibility = '';
4126 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4129 VT100.prototype.vt100 = function(s) {
4130 this.cursorNeedsShowing = this.hideCursor();
4131 this.respondString = '';
4133 for (var i = 0; i < s.length; i++) {
4134 var ch = s.charCodeAt(i);
4135 if (this.utfEnabled) {
4136 // Decode UTF8 encoded character
4138 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4139 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4140 if (--this.utfCount <= 0) {
4141 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4150 if ((ch & 0xE0) == 0xC0) {
4152 this.utfChar = ch & 0x1F;
4153 } else if ((ch & 0xF0) == 0xE0) {
4155 this.utfChar = ch & 0x0F;
4156 } else if ((ch & 0xF8) == 0xF0) {
4158 this.utfChar = ch & 0x07;
4159 } else if ((ch & 0xFC) == 0xF8) {
4161 this.utfChar = ch & 0x03;
4162 } else if ((ch & 0xFE) == 0xFC) {
4164 this.utfChar = ch & 0x01;
4174 var isNormalCharacter =
4175 (ch >= 32 && ch <= 127 || ch >= 160 ||
4176 this.utfEnabled && ch >= 128 ||
4177 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4178 (ch != 0x7F || this.dispCtrl);
4180 if (isNormalCharacter && this.isEsc == ESnormal) {
4182 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4184 if ((ch & 0xFF00) == 0xF000) {
4186 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4189 if (!this.printing) {
4190 if (this.needWrap || this.insertMode) {
4192 this.renderString(lineBuf);
4196 if (this.needWrap) {
4197 this.cr(); this.lf();
4199 if (this.insertMode) {
4200 this.scrollRegion(this.cursorX, this.cursorY,
4201 this.terminalWidth - this.cursorX - 1, 1,
4202 1, 0, this.color, this.style);
4205 this.lastCharacter = String.fromCharCode(ch);
4206 lineBuf += this.lastCharacter;
4207 if (!this.printing &&
4208 this.cursorX + lineBuf.length >= this.terminalWidth) {
4209 this.needWrap = this.autoWrapMode;
4213 this.renderString(lineBuf);
4216 var expand = this.doControl(ch);
4217 if (expand.length) {
4218 var r = this.respondString;
4219 this.respondString= r + this.vt100(expand);
4224 this.renderString(lineBuf, this.cursorNeedsShowing);
4225 } else if (this.cursorNeedsShowing) {
4228 return this.respondString;
4231 VT100.prototype.Latin1Map = [
4232 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4233 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4234 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4235 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4236 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4237 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4238 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4239 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4240 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4241 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4242 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4243 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4244 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4245 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4246 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4247 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4248 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4249 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4250 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4251 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4252 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4253 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4254 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4255 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4256 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4257 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4258 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4259 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4260 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4261 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4262 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4263 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4266 VT100.prototype.VT100GraphicsMap = [
4267 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4268 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4269 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4270 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4271 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4272 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4273 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4274 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4275 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4276 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4277 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4278 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4279 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4280 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4281 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4282 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4283 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4284 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4285 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4286 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4287 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4288 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4289 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4290 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4291 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4292 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4293 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4294 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4295 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4296 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4297 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4298 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4301 VT100.prototype.CodePage437Map = [
4302 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4303 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4304 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4305 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4306 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4307 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4308 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4309 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4310 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4311 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4312 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4313 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4314 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4315 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4316 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4317 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4318 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4319 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4320 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4321 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4322 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4323 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4324 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4325 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4326 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4327 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4328 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4329 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4330 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4331 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4332 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4333 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4336 VT100.prototype.DirectToFontMap = [
4337 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4338 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4339 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4340 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4341 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4342 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4343 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4344 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4345 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4346 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4347 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4348 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4349 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4350 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4351 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4352 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4353 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4354 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4355 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4356 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4357 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4358 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4359 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4360 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4361 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4362 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4363 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4364 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4365 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4366 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4367 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4368 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4371 VT100.prototype.ctrlAction = [
4372 true, false, false, false, false, false, false, true,
4373 true, true, true, true, true, true, true, true,
4374 false, false, false, false, false, false, false, false,
4375 true, false, true, true, false, false, false, false
4378 VT100.prototype.ctrlAlways = [
4379 true, false, false, false, false, false, false, false,
4380 true, false, true, false, true, true, true, true,
4381 false, false, false, false, false, false, false, false,
4382 false, false, false, true, false, false, false, false