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.titleString = '';
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;
720 if (child.nodeName == "#text") {
721 // If the key only has a text node as a child, then it is a letter.
722 // Automatically compute the lower and upper case version of the
724 var text = this.getTextContent(child) ||
725 this.getTextContent(elem);
726 this.addKeyBinding(elem, text.toLowerCase());
727 } else if (child.nextSibling) {
728 // If the key has two children, they are the lower and upper case
729 // character code, respectively.
730 this.addKeyBinding(elem, this.getTextContent(child), undefined,
731 this.getTextContent(child.nextSibling));
737 // Recursively parse all other child nodes.
738 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
739 this.initializeKeyBindings(elem);
743 VT100.prototype.initializeKeyboardButton = function() {
744 // Configure mouse event handlers for button that displays/hides keyboard
745 this.addListener(this.keyboardImage, 'click',
746 function(vt100) { return function(e) {
747 if (vt100.keyboard.style.display != '') {
748 if (vt100.reconnectBtn.style.visibility != '') {
749 vt100.initializeKeyboard();
750 vt100.showSoftKeyboard();
753 vt100.hideSoftKeyboard();
756 return false; }; }(this));
758 // Enable button that displays keyboard
759 if (this.softKeyboard) {
760 this.keyboardImage.style.visibility = 'visible';
764 VT100.prototype.initializeKeyboard = function() {
765 // Only need to initialize the keyboard the very first time. When doing so,
766 // copy the keyboard layout from the iframe.
767 if (this.keyboard.firstChild) {
770 this.keyboard.innerHTML =
771 this.layout.contentDocument.body.innerHTML;
772 var box = this.keyboard.firstChild;
773 this.hideSoftKeyboard();
775 // Configure mouse event handlers for on-screen keyboard
776 this.addListener(this.keyboard, 'click',
777 function(vt100) { return function(e) {
778 vt100.hideSoftKeyboard();
780 return false; }; }(this));
781 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
782 this.addListener(box, 'click', this.cancelEvent);
783 this.addListener(box, 'mouseup',
784 function(vt100) { return function(e) {
785 if (vt100.lastSelectedKey) {
786 vt100.lastSelectedKey.className = '';
787 vt100.lastSelectedKey = undefined;
789 return false; }; }(this));
790 this.addListener(box, 'mouseout',
791 function(vt100) { return function(e) {
792 return vt100.resetLastSelectedKey(e); }; }(this));
793 this.addListener(box, 'mouseover',
794 function(vt100) { return function(e) {
795 return vt100.resetLastSelectedKey(e); }; }(this));
797 // Configure SHIFT key behavior
798 var style = document.createElement('style');
799 var id = document.createAttribute('id');
800 id.nodeValue = 'shift_state';
801 style.setAttributeNode(id);
802 var type = document.createAttribute('type');
803 type.nodeValue = 'text/css';
804 style.setAttributeNode(type);
805 document.getElementsByTagName('head')[0].appendChild(style);
807 // Set up key bindings
808 this.initializeKeyBindings(box);
811 VT100.prototype.initializeElements = function(container) {
812 // If the necessary objects have not already been defined in the HTML
813 // page, create them now.
815 this.container = container;
816 } else if (!(this.container = document.getElementById('vt100'))) {
817 this.container = document.createElement('div');
818 this.container.id = 'vt100';
819 document.body.appendChild(this.container);
822 if (!this.getChildById(this.container, 'reconnect') ||
823 !this.getChildById(this.container, 'menu') ||
824 !this.getChildById(this.container, 'keyboard') ||
825 !this.getChildById(this.container, 'kbd_button') ||
826 !this.getChildById(this.container, 'kbd_img') ||
827 !this.getChildById(this.container, 'layout') ||
828 !this.getChildById(this.container, 'scrollable') ||
829 !this.getChildById(this.container, 'console') ||
830 !this.getChildById(this.container, 'alt_console') ||
831 !this.getChildById(this.container, 'ieprobe') ||
832 !this.getChildById(this.container, 'padding') ||
833 !this.getChildById(this.container, 'cursor') ||
834 !this.getChildById(this.container, 'lineheight') ||
835 !this.getChildById(this.container, 'usercss') ||
836 !this.getChildById(this.container, 'space') ||
837 !this.getChildById(this.container, 'input') ||
838 !this.getChildById(this.container, 'cliphelper')) {
839 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
840 // we might get a pointless warning that a suitable plugin is not yet
841 // installed. If in doubt, we'd rather just stay silent.
844 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
846 embed = typeof suppressAllAudio != 'undefined' &&
847 suppressAllAudio ? "" :
848 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
851 'autostart="false" ' +
853 'enablejavascript="true" ' +
854 'type="audio/x-wav" ' +
857 'style="position:absolute;left:-1000px;top:-1000px" />';
862 this.container.innerHTML =
863 '<div id="reconnect" style="visibility: hidden">' +
864 '<input type="button" value="Connect" ' +
865 'onsubmit="return false" />' +
867 '<div id="cursize" style="visibility: hidden">' +
869 '<div id="menu"></div>' +
870 '<div id="keyboard" unselectable="on">' +
872 '<div id="scrollable">' +
873 '<table id="kbd_button">' +
874 '<tr><td width="100%"> </td>' +
875 '<td><img id="kbd_img" src="keyboard.png" /></td>' +
876 '<td> </td></tr>' +
878 '<pre id="lineheight"> </pre>' +
879 '<pre id="console">' +
881 '<div id="ieprobe"><span> </span></div>' +
883 '<pre id="alt_console" style="display: none"></pre>' +
884 '<div id="padding"></div>' +
885 '<pre id="cursor"> </pre>' +
887 '<div class="hidden">' +
888 '<div id="usercss"></div>' +
889 '<pre><div><span id="space"></span></div></pre>' +
890 '<input type="textfield" id="input" />' +
891 '<input type="textfield" id="cliphelper" />' +
892 (typeof suppressAllAudio != 'undefined' &&
893 suppressAllAudio ? "" :
894 embed + '<bgsound id="beep_bgsound" loop=1 />') +
895 '<iframe id="layout" src="keyboard.html" />' +
899 // Find the object used for playing the "beep" sound, if any.
900 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
901 this.beeper = undefined;
903 this.beeper = this.getChildById(this.container,
905 if (!this.beeper || !this.beeper.Play) {
906 this.beeper = this.getChildById(this.container,
908 if (!this.beeper || typeof this.beeper.src == 'undefined') {
909 this.beeper = undefined;
914 // Initialize the variables for finding the text console and the
916 this.reconnectBtn = this.getChildById(this.container,'reconnect');
917 this.curSizeBox = this.getChildById(this.container, 'cursize');
918 this.menu = this.getChildById(this.container, 'menu');
919 this.keyboard = this.getChildById(this.container, 'keyboard');
920 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
921 this.layout = this.getChildById(this.container, 'layout');
922 this.scrollable = this.getChildById(this.container,
924 this.lineheight = this.getChildById(this.container,
927 [ this.getChildById(this.container, 'console'),
928 this.getChildById(this.container, 'alt_console') ];
929 var ieProbe = this.getChildById(this.container, 'ieprobe');
930 this.padding = this.getChildById(this.container, 'padding');
931 this.cursor = this.getChildById(this.container, 'cursor');
932 this.usercss = this.getChildById(this.container, 'usercss');
933 this.space = this.getChildById(this.container, 'space');
934 this.input = this.getChildById(this.container, 'input');
935 this.cliphelper = this.getChildById(this.container,
938 // Add any user selectable style sheets to the menu
939 this.initializeUserCSSStyles();
941 // Remember the dimensions of a standard character glyph. We would
942 // expect that we could just check cursor.clientWidth/Height at any time,
943 // but it turns out that browsers sometimes invalidate these values
944 // (e.g. while displaying a print preview screen).
945 this.cursorWidth = this.cursor.clientWidth;
946 this.cursorHeight = this.lineheight.clientHeight;
948 // IE has a slightly different boxing model, that we need to compensate for
949 this.isIE = ieProbe.offsetTop > 1;
951 this.console.innerHTML = '';
953 // Determine if the terminal window is positioned at the beginning of the
954 // page, or if it is embedded somewhere else in the page. For full-screen
955 // terminals, automatically resize whenever the browser window changes.
956 var marginTop = parseInt(this.getCurrentComputedStyle(
957 document.body, 'marginTop'));
958 var marginLeft = parseInt(this.getCurrentComputedStyle(
959 document.body, 'marginLeft'));
960 var marginRight = parseInt(this.getCurrentComputedStyle(
961 document.body, 'marginRight'));
962 var x = this.container.offsetLeft;
963 var y = this.container.offsetTop;
964 for (var parent = this.container; parent = parent.offsetParent; ) {
965 x += parent.offsetLeft;
966 y += parent.offsetTop;
968 this.isEmbedded = marginTop != y ||
970 (window.innerWidth ||
971 document.documentElement.clientWidth ||
972 document.body.clientWidth) -
973 marginRight != x + this.container.offsetWidth;
974 if (!this.isEmbedded) {
975 // Some browsers generate resize events when the terminal is first
976 // shown. Disable showing the size indicator until a little bit after
977 // the terminal has been rendered the first time.
978 this.indicateSize = false;
979 setTimeout(function(vt100) {
981 vt100.indicateSize = true;
984 this.addListener(window, 'resize',
987 vt100.hideContextMenu();
989 vt100.showCurrentSize();
993 // Hide extra scrollbars attached to window
994 document.body.style.margin = '0px';
995 try { document.body.style.overflow ='hidden'; } catch (e) { }
996 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
999 // Set up onscreen soft keyboard
1000 this.initializeKeyboardButton();
1002 // Hide context menu
1003 this.hideContextMenu();
1005 // Add listener to reconnect button
1006 this.addListener(this.reconnectBtn.firstChild, 'click',
1009 var rc = vt100.reconnect();
1010 vt100.input.focus();
1015 // Add input listeners
1016 this.addListener(this.input, 'blur',
1018 return function() { vt100.blurCursor(); } }(this));
1019 this.addListener(this.input, 'focus',
1021 return function() { vt100.focusCursor(); } }(this));
1022 this.addListener(this.input, 'keydown',
1024 return function(e) {
1025 if (!e) e = window.event;
1026 return vt100.keyDown(e); } }(this));
1027 this.addListener(this.input, 'keypress',
1029 return function(e) {
1030 if (!e) e = window.event;
1031 return vt100.keyPressed(e); } }(this));
1032 this.addListener(this.input, 'keyup',
1034 return function(e) {
1035 if (!e) e = window.event;
1036 return vt100.keyUp(e); } }(this));
1038 // Attach listeners that move the focus to the <input> field. This way we
1039 // can make sure that we can receive keyboard input.
1040 var mouseEvent = function(vt100, type) {
1041 return function(e) {
1042 if (!e) e = window.event;
1043 return vt100.mouseEvent(e, type);
1046 this.addListener(this.scrollable,'mousedown',mouseEvent(this, MOUSE_DOWN));
1047 this.addListener(this.scrollable,'mouseup', mouseEvent(this, MOUSE_UP));
1048 this.addListener(this.scrollable,'click', mouseEvent(this, MOUSE_CLICK));
1050 // Initialize the blank terminal window.
1051 this.currentScreen = 0;
1054 this.numScrollbackLines = 0;
1056 this.bottom = 0x7FFFFFFF;
1063 VT100.prototype.getChildById = function(parent, id) {
1064 var nodeList = parent.all || parent.getElementsByTagName('*');
1065 if (typeof nodeList.namedItem == 'undefined') {
1066 for (var i = 0; i < nodeList.length; i++) {
1067 if (nodeList[i].id == id) {
1073 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1074 return elem ? elem[0] || elem : null;
1078 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1079 if (typeof elem.currentStyle != 'undefined') {
1080 return elem.currentStyle[style];
1082 return document.defaultView.getComputedStyle(elem, null)[style];
1086 VT100.prototype.reconnect = function() {
1090 VT100.prototype.showReconnect = function(state) {
1092 this.hideSoftKeyboard();
1093 this.reconnectBtn.style.visibility = '';
1095 this.reconnectBtn.style.visibility = 'hidden';
1099 VT100.prototype.repairElements = function(console) {
1100 for (var line = console.firstChild; line; line = line.nextSibling) {
1101 if (!line.clientHeight) {
1102 var newLine = document.createElement(line.tagName);
1103 newLine.style.cssText = line.style.cssText;
1104 newLine.className = line.className;
1105 if (line.tagName == 'DIV') {
1106 for (var span = line.firstChild; span; span = span.nextSibling) {
1107 var newSpan = document.createElement(span.tagName);
1108 newSpan.style.cssText = span.style.cssText;
1109 newSpan.style.className = span.style.className;
1110 this.setTextContent(newSpan, this.getTextContent(span));
1111 newLine.appendChild(newSpan);
1114 this.setTextContent(newLine, this.getTextContent(line));
1116 line.parentNode.replaceChild(newLine, line);
1122 VT100.prototype.resized = function(w, h) {
1125 VT100.prototype.resizer = function() {
1126 // Hide onscreen soft keyboard
1127 this.hideSoftKeyboard();
1129 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1130 // Recreating it, will repair it.
1131 var newCursor = document.createElement('pre');
1132 this.setTextContent(newCursor, ' ');
1133 newCursor.id = 'cursor';
1134 newCursor.style.cssText = this.cursor.style.cssText;
1135 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1136 if (!newCursor.clientHeight) {
1137 // Things are broken right now. This is probably because we are
1138 // displaying the print-preview. Just don't change any of our settings
1139 // until the print dialog is closed again.
1140 newCursor.parentNode.removeChild(newCursor);
1143 // Swap the old broken cursor for the newly created one.
1144 this.cursor.parentNode.removeChild(this.cursor);
1145 this.cursor = newCursor;
1148 // Really horrible things happen if the contents of the terminal changes
1149 // while the print-preview is showing. We get HTML elements that show up
1150 // in the DOM, but that do not take up any space. Find these elements and
1152 this.repairElements(this.console[0]);
1153 this.repairElements(this.console[1]);
1155 // Lock the cursor size to the size of a normal character. This helps with
1156 // characters that are taller/shorter than normal. Unfortunately, we will
1157 // still get confused if somebody enters a character that is wider/narrower
1158 // than normal. This can happen if the browser tries to substitute a
1159 // characters from a different font.
1160 this.cursor.style.width = this.cursorWidth + 'px';
1161 this.cursor.style.height = this.cursorHeight + 'px';
1163 // Adjust height for one pixel padding of the #vt100 element.
1164 // The latter is necessary to properly display the inactive cursor.
1165 var console = this.console[this.currentScreen];
1166 var height = (this.isEmbedded ? this.container.clientHeight
1167 : (window.innerHeight ||
1168 document.documentElement.clientHeight ||
1169 document.body.clientHeight))-1;
1170 var partial = height % this.cursorHeight;
1171 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1172 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1173 var oldTerminalHeight = this.terminalHeight;
1175 this.updateHeight();
1177 // Clip the cursor to the visible screen.
1178 var cx = this.cursorX;
1179 var cy = this.cursorY + this.numScrollbackLines;
1181 // The alternate screen never keeps a scroll back buffer.
1182 this.updateNumScrollbackLines();
1183 while (this.currentScreen && this.numScrollbackLines > 0) {
1184 console.removeChild(console.firstChild);
1185 this.numScrollbackLines--;
1187 cy -= this.numScrollbackLines;
1190 } else if (cx > this.terminalWidth) {
1191 cx = this.terminalWidth - 1;
1198 } else if (cy > this.terminalHeight) {
1199 cy = this.terminalHeight - 1;
1205 // Clip the scroll region to the visible screen.
1206 if (this.bottom > this.terminalHeight ||
1207 this.bottom == oldTerminalHeight) {
1208 this.bottom = this.terminalHeight;
1210 if (this.top >= this.bottom) {
1211 this.top = this.bottom-1;
1217 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1218 // particularly important after changing the screen number), and reset
1219 // the scroll region to the default.
1220 this.truncateLines(this.terminalWidth);
1221 this.putString(cx, cy, '', undefined);
1222 this.scrollable.scrollTop = this.numScrollbackLines *
1223 this.cursorHeight + 1;
1225 // Update classNames for lines in the scrollback buffer
1226 var line = console.firstChild;
1227 for (var i = 0; i < this.numScrollbackLines; i++) {
1228 line.className = 'scrollback';
1229 line = line.nextSibling;
1232 line.className = '';
1233 line = line.nextSibling;
1236 // Reposition the reconnect button
1237 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1239 this.reconnectBtn.clientWidth)/2 + 'px';
1240 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1241 this.reconnectBtn.clientHeight)/2 + 'px';
1243 // Send notification that the window size has been changed
1244 this.resized(this.terminalWidth, this.terminalHeight);
1247 VT100.prototype.showCurrentSize = function() {
1248 if (!this.indicateSize) {
1251 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1252 this.terminalHeight;
1253 this.curSizeBox.style.left =
1254 (this.terminalWidth*this.cursorWidth/
1256 this.curSizeBox.clientWidth)/2 + 'px';
1257 this.curSizeBox.style.top =
1258 (this.terminalHeight*this.cursorHeight -
1259 this.curSizeBox.clientHeight)/2 + 'px';
1260 this.curSizeBox.style.visibility = '';
1261 if (this.curSizeTimeout) {
1262 clearTimeout(this.curSizeTimeout);
1265 // Only show the terminal size for a short amount of time after resizing.
1266 // Then hide this information, again. Some browsers generate resize events
1267 // throughout the entire resize operation. This is nice, and we will show
1268 // the terminal size while the user is dragging the window borders.
1269 // Other browsers only generate a single event when the user releases the
1270 // mouse. In those cases, we can only show the terminal size once at the
1271 // end of the resize operation.
1272 this.curSizeTimeout = setTimeout(function(vt100) {
1274 vt100.curSizeTimeout = null;
1275 vt100.curSizeBox.style.visibility = 'hidden';
1280 VT100.prototype.selection = function() {
1282 return '' + (window.getSelection && window.getSelection() ||
1283 document.selection && document.selection.type == 'Text' &&
1284 document.selection.createRange().text || '');
1290 VT100.prototype.cancelEvent = function(event) {
1292 // For non-IE browsers
1293 event.stopPropagation();
1294 event.preventDefault();
1299 event.cancelBubble = true;
1300 event.returnValue = false;
1308 VT100.prototype.mousePosition = function(event) {
1309 var offsetX = this.container.offsetLeft;
1310 var offsetY = this.container.offsetTop;
1311 for (var e = this.container; e = e.offsetParent; ) {
1312 offsetX += e.offsetLeft;
1313 offsetY += e.offsetTop;
1315 return [ event.clientX - offsetX,
1316 event.clientY - offsetY ];
1319 VT100.prototype.mouseEvent = function(event, type) {
1320 // If any text is currently selected, do not move the focus as that would
1321 // invalidate the selection.
1322 var selection = this.selection();
1323 if ((type == MOUSE_UP || type == MOUSE_CLICK) && !selection.length) {
1327 // Compute mouse position in characters.
1328 var position = this.mousePosition(event);
1329 var x = Math.floor(position[0] / this.cursorWidth);
1330 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1331 this.cursorHeight) - this.numScrollbackLines;
1333 if (x >= this.terminalWidth) {
1334 x = this.terminalWidth - 1;
1341 if (y >= this.terminalHeight) {
1342 y = this.terminalHeight - 1;
1350 // Compute button number and modifier keys.
1351 var button = type != MOUSE_DOWN ? 3 :
1352 typeof event.pageX != 'undefined' ? event.button :
1353 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1354 if (button != undefined) {
1355 if (event.shiftKey) {
1358 if (event.altKey || event.metaKey) {
1361 if (event.ctrlKey) {
1366 // Report mouse events if they happen inside of the current screen and
1367 // with the SHIFT key unpressed. Both of these restrictions do not apply
1368 // for button releases, as we always want to report those.
1369 if (this.mouseReporting && !selection.length &&
1370 (type != MOUSE_DOWN || !event.shiftKey)) {
1371 if (inside || type != MOUSE_DOWN) {
1372 if (button != undefined) {
1373 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1374 String.fromCharCode(x + 33) +
1375 String.fromCharCode(y + 33);
1376 if (type != MOUSE_CLICK) {
1377 this.keysPressed(report);
1380 // If we reported the event, stop propagating it (not sure, if this
1381 // actually works on most browsers; blocking the global "oncontextmenu"
1382 // even is still necessary).
1383 return this.cancelEvent(event);
1388 // Bring up context menu.
1389 if (button == 2 && !event.shiftKey) {
1390 if (type == MOUSE_DOWN) {
1391 this.showContextMenu(position[0], position[1]);
1393 return this.cancelEvent(event);
1396 if (this.mouseReporting) {
1398 event.shiftKey = false;
1406 VT100.prototype.replaceChar = function(s, ch, repl) {
1407 for (var i = -1;;) {
1408 i = s.indexOf(ch, i + 1);
1412 s = s.substr(0, i) + repl + s.substr(i + 1);
1417 VT100.prototype.htmlEscape = function(s) {
1418 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1419 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1422 VT100.prototype.getTextContent = function(elem) {
1423 return elem.textContent ||
1424 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1427 VT100.prototype.setTextContentRaw = function(elem, s) {
1428 // Updating the content of an element is an expensive operation. It actually
1429 // pays off to first check whether the element is still unchanged.
1430 if (typeof elem.textContent == 'undefined') {
1431 if (elem.innerText != s) {
1435 // Very old versions of IE do not allow setting innerText. Instead,
1436 // remove all children, by setting innerHTML and then set the text
1437 // using DOM methods.
1438 elem.innerHTML = '';
1439 elem.appendChild(document.createTextNode(
1440 this.replaceChar(s, ' ', '\u00A0')));
1444 if (elem.textContent != s) {
1445 elem.textContent = s;
1450 VT100.prototype.setTextContent = function(elem, s) {
1451 // Check if we find any URLs in the text. If so, automatically convert them
1453 if (this.urlRE && this.urlRE.test(s)) {
1457 if (RegExp.leftContext != null) {
1458 inner += this.htmlEscape(RegExp.leftContext);
1459 consumed += RegExp.leftContext.length;
1461 var url = this.htmlEscape(RegExp.lastMatch);
1464 // If no protocol was specified, try to guess a reasonable one.
1465 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1466 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1467 var slash = url.indexOf('/');
1468 var at = url.indexOf('@');
1469 var question = url.indexOf('?');
1471 (at < question || question < 0) &&
1472 (slash < 0 || (question > 0 && slash > question))) {
1473 fullUrl = 'mailto:' + url;
1475 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1480 inner += '<a target="vt100Link" href="' + fullUrl +
1481 '">' + url + '</a>';
1482 consumed += RegExp.lastMatch.length;
1483 s = s.substr(consumed);
1484 if (!this.urlRE.test(s)) {
1485 if (RegExp.rightContext != null) {
1486 inner += this.htmlEscape(RegExp.rightContext);
1491 elem.innerHTML = inner;
1495 this.setTextContentRaw(elem, s);
1498 VT100.prototype.insertBlankLine = function(y, color, style) {
1499 // Insert a blank line a position y. This method ignores the scrollback
1500 // buffer. The caller has to add the length of the scrollback buffer to
1501 // the position, if necessary.
1502 // If the position is larger than the number of current lines, this
1503 // method just adds a new line right after the last existing one. It does
1504 // not add any missing lines in between. It is the caller's responsibility
1507 color = 'ansi0 bgAnsi15';
1513 if (color != 'ansi0 bgAnsi15' && !style) {
1514 line = document.createElement('pre');
1515 this.setTextContent(line, '\n');
1517 line = document.createElement('div');
1518 var span = document.createElement('span');
1519 span.style.cssText = style;
1520 span.style.className = color;
1521 this.setTextContent(span, this.spaces(this.terminalWidth));
1522 line.appendChild(span);
1524 line.style.height = this.cursorHeight + 'px';
1525 var console = this.console[this.currentScreen];
1526 if (console.childNodes.length > y) {
1527 console.insertBefore(line, console.childNodes[y]);
1529 console.appendChild(line);
1533 VT100.prototype.updateWidth = function() {
1534 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1535 this.cursorWidth*this.scale);
1536 return this.terminalWidth;
1539 VT100.prototype.updateHeight = function() {
1540 // We want to be able to display either a terminal window that fills the
1541 // entire browser window, or a terminal window that is contained in a
1542 // <div> which is embededded somewhere in the web page.
1543 if (this.isEmbedded) {
1544 // Embedded terminal. Use size of the containing <div> (id="vt100").
1545 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1548 // Use the full browser window.
1549 this.terminalHeight = Math.floor(((window.innerHeight ||
1550 document.documentElement.clientHeight ||
1551 document.body.clientHeight)-1)/
1554 return this.terminalHeight;
1557 VT100.prototype.updateNumScrollbackLines = function() {
1558 var scrollback = Math.floor(
1559 this.console[this.currentScreen].offsetHeight /
1560 this.cursorHeight) -
1561 this.terminalHeight;
1562 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1563 return this.numScrollbackLines;
1566 VT100.prototype.truncateLines = function(width) {
1570 for (var line = this.console[this.currentScreen].firstChild; line;
1571 line = line.nextSibling) {
1572 if (line.tagName == 'DIV') {
1575 // Traverse current line and truncate it once we saw "width" characters
1576 for (var span = line.firstChild; span;
1577 span = span.nextSibling) {
1578 var s = this.getTextContent(span);
1580 if (x + l > width) {
1581 this.setTextContent(span, s.substr(0, width - x));
1582 while (span.nextSibling) {
1583 line.removeChild(line.lastChild);
1589 // Prune white space from the end of the current line
1590 var span = line.lastChild;
1592 span.className == 'ansi0 bgAnsi15' &&
1593 !span.style.cssText.length) {
1594 // Scan backwards looking for first non-space character
1595 var s = this.getTextContent(span);
1596 for (var i = s.length; i--; ) {
1597 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1598 if (i+1 != s.length) {
1599 this.setTextContent(s.substr(0, i+1));
1607 span = span.previousSibling;
1609 // Remove blank <span>'s from end of line
1610 line.removeChild(sibling);
1612 // Remove entire line (i.e. <div>), if empty
1613 var blank = document.createElement('pre');
1614 blank.style.height = this.cursorHeight + 'px';
1615 this.setTextContent(blank, '\n');
1616 line.parentNode.replaceChild(blank, line);
1624 VT100.prototype.putString = function(x, y, text, color, style) {
1626 color = 'ansi0 bgAnsi15';
1631 var yIdx = y + this.numScrollbackLines;
1637 var console = this.console[this.currentScreen];
1638 if (!text.length && (yIdx >= console.childNodes.length ||
1639 console.childNodes[yIdx].tagName != 'DIV')) {
1640 // Positioning cursor to a blank location
1643 // Create missing blank lines at end of page
1644 while (console.childNodes.length <= yIdx) {
1645 // In order to simplify lookups, we want to make sure that each line
1646 // is represented by exactly one element (and possibly a whole bunch of
1648 // For non-blank lines, we can create a <div> containing one or more
1649 // <span>s. For blank lines, this fails as browsers tend to optimize them
1650 // away. But fortunately, a <pre> tag containing a newline character
1651 // appears to work for all browsers (a would also work, but then
1652 // copying from the browser window would insert superfluous spaces into
1654 this.insertBlankLine(yIdx);
1656 line = console.childNodes[yIdx];
1658 // If necessary, promote blank '\n' line to a <div> tag
1659 if (line.tagName != 'DIV') {
1660 var div = document.createElement('div');
1661 div.style.height = this.cursorHeight + 'px';
1662 div.innerHTML = '<span></span>';
1663 console.replaceChild(div, line);
1667 // Scan through list of <span>'s until we find the one where our text
1669 span = line.firstChild;
1671 while (span.nextSibling && xPos < x) {
1672 len = this.getTextContent(span).length;
1673 if (xPos + len > x) {
1677 span = span.nextSibling;
1681 // If current <span> is not long enough, pad with spaces or add new
1683 s = this.getTextContent(span);
1684 var oldColor = span.className;
1685 var oldStyle = span.style.cssText;
1686 if (xPos + s.length < x) {
1687 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1688 span = document.createElement('span');
1689 line.appendChild(span);
1690 span.className = 'ansi0 bgAnsi15';
1691 span.style.cssText = '';
1692 oldColor = 'ansi0 bgAnsi15';
1699 } while (xPos + s.length < x);
1702 // If styles do not match, create a new <span>
1703 var del = text.length - s.length + x - xPos;
1704 if (oldColor != color ||
1705 (oldStyle != style && (oldStyle || style))) {
1707 // Replacing text at beginning of existing <span>
1708 if (text.length >= s.length) {
1709 // New text is equal or longer than existing text
1712 // Insert new <span> before the current one, then remove leading
1713 // part of existing <span>, adjust style of new <span>, and finally
1715 sibling = document.createElement('span');
1716 line.insertBefore(sibling, span);
1717 this.setTextContent(span, s.substr(text.length));
1722 // Replacing text some way into the existing <span>
1723 var remainder = s.substr(x + text.length - xPos);
1724 this.setTextContent(span, s.substr(0, x - xPos));
1726 sibling = document.createElement('span');
1727 if (span.nextSibling) {
1728 line.insertBefore(sibling, span.nextSibling);
1730 if (remainder.length) {
1731 sibling = document.createElement('span');
1732 sibling.className = oldColor;
1733 sibling.style.cssText = oldStyle;
1734 this.setTextContent(sibling, remainder);
1735 line.insertBefore(sibling, span.nextSibling);
1738 line.appendChild(sibling);
1740 if (remainder.length) {
1741 sibling = document.createElement('span');
1742 sibling.className = oldColor;
1743 sibling.style.cssText = oldStyle;
1744 this.setTextContent(sibling, remainder);
1745 line.appendChild(sibling);
1750 span.className = color;
1751 span.style.cssText = style;
1753 // Overwrite (partial) <span> with new text
1754 s = s.substr(0, x - xPos) +
1756 s.substr(x + text.length - xPos);
1758 this.setTextContent(span, s);
1761 // Delete all subsequent <span>'s that have just been overwritten
1762 sibling = span.nextSibling;
1763 while (del > 0 && sibling) {
1764 s = this.getTextContent(sibling);
1767 line.removeChild(sibling);
1769 sibling = span.nextSibling;
1771 this.setTextContent(sibling, s.substr(del));
1776 // Merge <span> with next sibling, if styles are identical
1777 if (sibling && span.className == sibling.className &&
1778 span.style.cssText == sibling.style.cssText) {
1779 this.setTextContent(span,
1780 this.getTextContent(span) +
1781 this.getTextContent(sibling));
1782 line.removeChild(sibling);
1788 this.cursorX = x + text.length;
1789 if (this.cursorX >= this.terminalWidth) {
1790 this.cursorX = this.terminalWidth - 1;
1791 if (this.cursorX < 0) {
1797 if (!this.cursor.style.visibility) {
1798 var idx = this.cursorX - xPos;
1800 // If we are in a non-empty line, take the cursor Y position from the
1801 // other elements in this line. If dealing with broken, non-proportional
1802 // fonts, this is likely to yield better results.
1803 pixelY = span.offsetTop +
1804 span.offsetParent.offsetTop;
1805 s = this.getTextContent(span);
1806 var nxtIdx = idx - s.length;
1808 this.setTextContent(this.cursor, s.charAt(idx));
1809 pixelX = span.offsetLeft +
1810 idx*span.offsetWidth / s.length;
1813 pixelX = span.offsetLeft + span.offsetWidth;
1815 if (span.nextSibling) {
1816 s = this.getTextContent(span.nextSibling);
1817 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1819 pixelX = span.nextSibling.offsetLeft +
1820 nxtIdx*span.offsetWidth / s.length;
1823 this.setTextContent(this.cursor, ' ');
1827 this.setTextContent(this.cursor, ' ');
1831 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
1834 this.setTextContent(this.space, this.spaces(this.cursorX));
1835 this.cursor.style.left = (this.space.offsetWidth +
1836 console.offsetLeft)/this.scale + 'px';
1838 this.cursorY = yIdx - this.numScrollbackLines;
1840 this.cursor.style.top = pixelY + 'px';
1842 this.cursor.style.top = yIdx*this.cursorHeight +
1843 console.offsetTop + 'px';
1847 // Merge <span> with previous sibling, if styles are identical
1848 if ((sibling = span.previousSibling) &&
1849 span.className == sibling.className &&
1850 span.style.cssText == sibling.style.cssText) {
1851 this.setTextContent(span,
1852 this.getTextContent(sibling) +
1853 this.getTextContent(span));
1854 line.removeChild(sibling);
1857 // Prune white space from the end of the current line
1858 span = line.lastChild;
1860 span.className == 'ansi0 bgAnsi15' &&
1861 !span.style.cssText.length) {
1862 // Scan backwards looking for first non-space character
1863 s = this.getTextContent(span);
1864 for (var i = s.length; i--; ) {
1865 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1866 if (i+1 != s.length) {
1867 this.setTextContent(s.substr(0, i+1));
1875 span = span.previousSibling;
1877 // Remove blank <span>'s from end of line
1878 line.removeChild(sibling);
1880 // Remove entire line (i.e. <div>), if empty
1881 var blank = document.createElement('pre');
1882 blank.style.height = this.cursorHeight + 'px';
1883 this.setTextContent(blank, '\n');
1884 line.parentNode.replaceChild(blank, line);
1891 VT100.prototype.gotoXY = function(x, y) {
1892 if (x >= this.terminalWidth) {
1893 x = this.terminalWidth - 1;
1899 if (this.offsetMode) {
1904 maxY = this.terminalHeight;
1912 this.putString(x, y, '', undefined);
1913 this.needWrap = false;
1916 VT100.prototype.gotoXaY = function(x, y) {
1917 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1920 VT100.prototype.refreshInvertedState = function() {
1921 if (this.isInverted) {
1922 this.scrollable.className += ' inverted';
1924 this.scrollable.className = this.scrollable.className.
1925 replace(/ *inverted/, '');
1929 VT100.prototype.enableAlternateScreen = function(state) {
1930 // Don't do anything, if we are already on the desired screen
1931 if ((state ? 1 : 0) == this.currentScreen) {
1932 // Calling the resizer is not actually necessary. But it is a good way
1933 // of resetting state that might have gotten corrupted.
1938 // We save the full state of the normal screen, when we switch away from it.
1939 // But for the alternate screen, no saving is necessary. We always reset
1940 // it when we switch to it.
1945 // Display new screen, and initialize state (the resizer does that for us).
1946 this.currentScreen = state ? 1 : 0;
1947 this.console[1-this.currentScreen].style.display = 'none';
1948 this.console[this.currentScreen].style.display = '';
1950 // Select appropriate character pitch.
1951 var transform = this.getTransformName();
1954 // Upon enabling the alternate screen, we switch to 80 column mode. But
1955 // upon returning to the regular screen, we restore the mode that was
1956 // in effect previously.
1957 this.console[1].style[transform] = '';
1960 this.console[this.currentScreen].style[transform];
1961 this.cursor.style[transform] = style;
1962 this.space.style[transform] = style;
1963 this.scale = style == '' ? 1.0:1.65;
1964 if (transform == 'filter') {
1965 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
1970 // If we switched to the alternate screen, reset it completely. Otherwise,
1971 // restore the saved state.
1974 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1976 this.restoreCursor();
1980 VT100.prototype.hideCursor = function() {
1981 var hidden = this.cursor.style.visibility == 'hidden';
1983 this.cursor.style.visibility = 'hidden';
1989 VT100.prototype.showCursor = function(x, y) {
1990 if (this.cursor.style.visibility) {
1991 this.cursor.style.visibility = '';
1992 this.putString(x == undefined ? this.cursorX : x,
1993 y == undefined ? this.cursorY : y,
2000 VT100.prototype.scrollBack = function() {
2001 var i = this.scrollable.scrollTop -
2002 this.scrollable.clientHeight;
2003 this.scrollable.scrollTop = i < 0 ? 0 : i;
2006 VT100.prototype.scrollFore = function() {
2007 var i = this.scrollable.scrollTop +
2008 this.scrollable.clientHeight;
2009 this.scrollable.scrollTop = i > this.numScrollbackLines *
2010 this.cursorHeight + 1
2011 ? this.numScrollbackLines *
2012 this.cursorHeight + 1
2016 VT100.prototype.spaces = function(i) {
2024 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2029 if (w > this.terminalWidth) {
2030 w = this.terminalWidth;
2032 if ((w -= x) <= 0) {
2039 if (h > this.terminalHeight) {
2040 h = this.terminalHeight;
2042 if ((h -= y) <= 0) {
2046 // Special case the situation where we clear the entire screen, and we do
2047 // not have a scrollback buffer. In that case, we should just remove all
2049 if (!this.numScrollbackLines &&
2050 w == this.terminalWidth && h == this.terminalHeight &&
2051 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2052 var console = this.console[this.currentScreen];
2053 while (console.lastChild) {
2054 console.removeChild(console.lastChild);
2056 this.putString(this.cursorX, this.cursorY, '', undefined);
2058 var hidden = this.hideCursor();
2059 var cx = this.cursorX;
2060 var cy = this.cursorY;
2061 var s = this.spaces(w);
2062 for (var i = y+h; i-- > y; ) {
2063 this.putString(x, i, s, color, style);
2065 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2069 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2071 var className = [ ];
2073 var console = this.console[this.currentScreen];
2074 if (sY >= console.childNodes.length) {
2075 text[0] = this.spaces(w);
2076 className[0] = undefined;
2077 style[0] = undefined;
2079 var line = console.childNodes[sY];
2080 if (line.tagName != 'DIV' || !line.childNodes.length) {
2081 text[0] = this.spaces(w);
2082 className[0] = undefined;
2083 style[0] = undefined;
2086 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2087 var s = this.getTextContent(span);
2090 var o = sX > x ? sX - x : 0;
2091 text[text.length] = s.substr(o, w);
2092 className[className.length] = span.className;
2093 style[style.length] = span.style.cssText;
2099 text[text.length] = this.spaces(w);
2100 className[className.length] = undefined;
2101 style[style.length] = undefined;
2105 var hidden = this.hideCursor();
2106 var cx = this.cursorX;
2107 var cy = this.cursorY;
2108 for (var i = 0; i < text.length; i++) {
2111 color = className[i];
2113 color = 'ansi0 bgAnsi15';
2115 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2116 dX += text[i].length;
2118 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2121 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2123 var left = incX < 0 ? -incX : 0;
2124 var right = incX > 0 ? incX : 0;
2125 var up = incY < 0 ? -incY : 0;
2126 var down = incY > 0 ? incY : 0;
2128 // Clip region against terminal size
2129 var dontScroll = null;
2134 if (w > this.terminalWidth - right) {
2135 w = this.terminalWidth - right;
2137 if ((w -= x) <= 0) {
2144 if (h > this.terminalHeight - down) {
2145 h = this.terminalHeight - down;
2151 if (style && style.indexOf('underline')) {
2152 // Different terminal emulators disagree on the attributes that
2153 // are used for scrolling. The consensus seems to be, never to
2154 // fill with underlined spaces. N.B. this is different from the
2155 // cases when the user blanks a region. User-initiated blanking
2156 // always fills with all of the current attributes.
2157 style = style.replace(/text-decoration:underline;/, '');
2160 // Compute current scroll position
2161 var scrollPos = this.numScrollbackLines -
2162 (this.scrollable.scrollTop-1) / this.cursorHeight;
2164 // Determine original cursor position. Hide cursor temporarily to avoid
2165 // visual artifacts.
2166 var hidden = this.hideCursor();
2167 var cx = this.cursorX;
2168 var cy = this.cursorY;
2169 var console = this.console[this.currentScreen];
2171 if (!incX && !x && w == this.terminalWidth) {
2172 // Scrolling entire lines
2175 if (!this.currentScreen && y == -incY &&
2176 h == this.terminalHeight + incY) {
2177 // Scrolling up with adding to the scrollback buffer. This is only
2178 // possible if there are at least as many lines in the console,
2179 // as the terminal is high
2180 while (console.childNodes.length < this.terminalHeight) {
2181 this.insertBlankLine(this.terminalHeight);
2184 // Add new lines at bottom in order to force scrolling
2185 for (var i = 0; i < y; i++) {
2186 this.insertBlankLine(console.childNodes.length, color, style);
2189 // Adjust the number of lines in the scrollback buffer by
2190 // removing excess entries.
2191 this.updateNumScrollbackLines();
2192 while (this.numScrollbackLines >
2193 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2194 console.removeChild(console.firstChild);
2195 this.numScrollbackLines--;
2198 // Mark lines in the scrollback buffer, so that they do not get
2200 for (var i = this.numScrollbackLines, j = -incY;
2201 i-- > 0 && j-- > 0; ) {
2202 console.childNodes[i].className = 'scrollback';
2205 // Scrolling up without adding to the scrollback buffer.
2208 console.childNodes.length >
2209 this.numScrollbackLines + y + incY; ) {
2210 console.removeChild(console.childNodes[
2211 this.numScrollbackLines + y + incY]);
2214 // If we used to have a scrollback buffer, then we must make sure
2215 // that we add back blank lines at the bottom of the terminal.
2216 // Similarly, if we are scrolling in the middle of the screen,
2217 // we must add blank lines to ensure that the bottom of the screen
2218 // does not move up.
2219 if (this.numScrollbackLines > 0 ||
2220 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2221 for (var i = -incY; i-- > 0; ) {
2222 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2231 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2232 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2234 for (var i = incY; i--; ) {
2235 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2239 // Scrolling partial lines
2241 // Scrolling up or horizontally within a line
2242 for (var i = y + this.numScrollbackLines;
2243 i < y + this.numScrollbackLines + h;
2245 this.copyLineSegment(x + incX, i + incY, x, i, w);
2249 for (var i = y + this.numScrollbackLines + h;
2250 i-- > y + this.numScrollbackLines; ) {
2251 this.copyLineSegment(x + incX, i + incY, x, i, w);
2255 // Clear blank regions
2257 this.clearRegion(x, y, incX, h, color, style);
2258 } else if (incX < 0) {
2259 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2262 this.clearRegion(x, y, w, incY, color, style);
2263 } else if (incY < 0) {
2264 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2268 // Reset scroll position
2269 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2270 this.cursorHeight + 1;
2272 // Move cursor back to its original position
2273 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2277 VT100.prototype.copy = function(selection) {
2278 if (selection == undefined) {
2279 selection = this.selection();
2281 this.internalClipboard = undefined;
2282 if (selection.length) {
2285 this.cliphelper.value = selection;
2286 this.cliphelper.select();
2287 this.cliphelper.createTextRange().execCommand('copy');
2289 this.internalClipboard = selection;
2291 this.cliphelper.value = '';
2295 VT100.prototype.copyLast = function() {
2296 // Opening the context menu can remove the selection. We try to prevent this
2297 // from happening, but that is not possible for all browsers. So, instead,
2298 // we compute the selection before showing the menu.
2299 this.copy(this.lastSelection);
2302 VT100.prototype.pasteFnc = function() {
2303 var clipboard = undefined;
2304 if (this.internalClipboard != undefined) {
2305 clipboard = this.internalClipboard;
2308 this.cliphelper.value = '';
2309 this.cliphelper.createTextRange().execCommand('paste');
2310 clipboard = this.cliphelper.value;
2314 this.cliphelper.value = '';
2315 if (clipboard && this.menu.style.visibility == 'hidden') {
2317 this.keysPressed('' + clipboard);
2324 VT100.prototype.toggleUTF = function() {
2325 this.utfEnabled = !this.utfEnabled;
2327 // We always persist the last value that the user selected. Not necessarily
2328 // the last value that a random program requested.
2329 this.utfPreferred = this.utfEnabled;
2332 VT100.prototype.toggleBell = function() {
2333 this.visualBell = !this.visualBell;
2336 VT100.prototype.toggleSoftKeyboard = function() {
2337 this.softKeyboard = !this.softKeyboard;
2338 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2341 VT100.prototype.deselectKeys = function(elem) {
2342 if (elem && elem.className == 'selected') {
2343 elem.className = '';
2345 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2346 this.deselectKeys(elem);
2350 VT100.prototype.showSoftKeyboard = function() {
2351 // Make sure no key is currently selected
2352 this.lastSelectedKey = undefined;
2353 this.deselectKeys(this.keyboard);
2354 this.isShift = false;
2355 this.showShiftState(false);
2356 this.isCtrl = false;
2357 this.showCtrlState(false);
2359 this.showAltState(false);
2361 this.keyboard.style.left = '0px';
2362 this.keyboard.style.top = '0px';
2363 this.keyboard.style.width = this.container.offsetWidth + 'px';
2364 this.keyboard.style.height = this.container.offsetHeight + 'px';
2365 this.keyboard.style.visibility = 'hidden';
2366 this.keyboard.style.display = '';
2368 var kbd = this.keyboard.firstChild;
2370 var transform = this.getTransformName();
2372 kbd.style[transform] = '';
2373 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2374 scale = (kbd.offsetWidth/
2375 this.container.offsetWidth)/0.9;
2377 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2378 scale = Math.max((kbd.offsetHeight/
2379 this.container.offsetHeight)/0.9);
2381 var style = this.getTransformStyle(transform,
2382 scale > 1.0 ? scale : undefined);
2383 kbd.style[transform] = style;
2385 if (transform == 'filter') {
2388 kbd.style.left = ((this.container.offsetWidth -
2389 kbd.offsetWidth/scale)/2) + 'px';
2390 kbd.style.top = ((this.container.offsetHeight -
2391 kbd.offsetHeight/scale)/2) + 'px';
2393 this.keyboard.style.visibility = 'visible';
2396 VT100.prototype.hideSoftKeyboard = function() {
2397 this.keyboard.style.display = 'none';
2400 VT100.prototype.toggleCursorBlinking = function() {
2401 this.blinkingCursor = !this.blinkingCursor;
2402 this.animateCursor('bright');
2405 VT100.prototype.about = function() {
2406 alert("VT100 Terminal Emulator " + VERSION +
2407 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2408 "For more information check http://shellinabox.com");
2411 VT100.prototype.hideContextMenu = function() {
2412 this.menu.style.visibility = 'hidden';
2413 this.menu.style.top = '-100px';
2414 this.menu.style.left = '-100px';
2415 this.menu.style.width = '0px';
2416 this.menu.style.height = '0px';
2419 VT100.prototype.extendContextMenu = function(entries, actions) {
2422 VT100.prototype.showContextMenu = function(x, y) {
2423 this.menu.innerHTML =
2424 '<table class="popup" ' +
2425 'cellpadding="0" cellspacing="0">' +
2427 '<ul id="menuentries">' +
2428 '<li id="beginclipboard">Copy</li>' +
2429 '<li id="endclipboard">Paste</li>' +
2431 '<li id="reset">Reset</li>' +
2433 '<li id="beginconfig">' +
2434 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2437 (this.visualBell ? '<img src="enabled.gif" />' : '') +
2440 (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2441 'Onscreen Keyboard</li>' +
2442 '<li id="endconfig">' +
2443 (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2444 'Blinking Cursor</li>'+
2445 (this.usercss.firstChild ?
2446 '<hr id="beginusercss" />' +
2447 this.usercss.innerHTML +
2448 '<hr id="endusercss" />' :
2450 '<li id="about">About...</li>' +
2455 var popup = this.menu.firstChild;
2456 var menuentries = this.getChildById(popup, 'menuentries');
2458 // Determine menu entries that should be disabled
2459 this.lastSelection = this.selection();
2460 if (!this.lastSelection.length) {
2461 menuentries.firstChild.className
2464 var p = this.pasteFnc();
2466 menuentries.childNodes[1].className
2470 // Actions for default items
2471 var actions = [ this.copyLast, p, this.reset,
2472 this.toggleUTF, this.toggleBell,
2473 this.toggleSoftKeyboard,
2474 this.toggleCursorBlinking ];
2476 // Actions for user CSS styles (if any)
2477 for (var i = 0; i < this.usercssActions.length; ++i) {
2478 actions[actions.length] = this.usercssActions[i];
2480 actions[actions.length] = this.about;
2482 // Allow subclasses to dynamically add entries to the context menu
2483 this.extendContextMenu(menuentries, actions);
2485 // Hook up event listeners
2486 for (var node = menuentries.firstChild, i = 0; node;
2487 node = node.nextSibling) {
2488 if (node.tagName == 'LI') {
2489 if (node.className != 'disabled') {
2490 this.addListener(node, 'mouseover',
2491 function(vt100, node) {
2493 node.className = 'hover';
2496 this.addListener(node, 'mouseout',
2497 function(vt100, node) {
2499 node.className = '';
2502 this.addListener(node, 'mousedown',
2503 function(vt100, action) {
2504 return function(event) {
2505 vt100.hideContextMenu();
2507 vt100.storeUserSettings();
2508 return vt100.cancelEvent(event || window.event);
2510 }(this, actions[i]));
2511 this.addListener(node, 'mouseup',
2513 return function(event) {
2514 return vt100.cancelEvent(event || window.event);
2517 this.addListener(node, 'mouseclick',
2519 return function(event) {
2520 return vt100.cancelEvent(event || window.event);
2528 // Position menu next to the mouse pointer
2529 this.menu.style.left = '0px';
2530 this.menu.style.top = '0px';
2531 this.menu.style.width = this.container.offsetWidth + 'px';
2532 this.menu.style.height = this.container.offsetHeight + 'px';
2533 popup.style.left = '0px';
2534 popup.style.top = '0px';
2537 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2538 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2543 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2544 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2549 popup.style.left = x + 'px';
2550 popup.style.top = y + 'px';
2552 // Block all other interactions with the terminal emulator
2553 this.addListener(this.menu, 'click', function(vt100) {
2555 vt100.hideContextMenu();
2560 this.menu.style.visibility = '';
2563 VT100.prototype.keysPressed = function(ch) {
2564 for (var i = 0; i < ch.length; i++) {
2565 var c = ch.charCodeAt(i);
2566 this.vt100(c >= 7 && c <= 15 ||
2567 c == 24 || c == 26 || c == 27 || c >= 32
2568 ? String.fromCharCode(c) : '<' + c + '>');
2572 VT100.prototype.applyModifiers = function(ch, event) {
2574 if (event.ctrlKey) {
2575 if (ch >= 32 && ch <= 127) {
2576 // For historic reasons, some control characters are treated specially
2578 case /* 3 */ 51: ch = 27; break;
2579 case /* 4 */ 52: ch = 28; break;
2580 case /* 5 */ 53: ch = 29; break;
2581 case /* 6 */ 54: ch = 30; break;
2582 case /* 7 */ 55: ch = 31; break;
2583 case /* 8 */ 56: ch = 127; break;
2584 case /* ? */ 63: ch = 127; break;
2585 default: ch &= 31; break;
2589 return String.fromCharCode(ch);
2595 VT100.prototype.handleKey = function(event) {
2596 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2597 // (event.shiftKey || event.ctrlKey || event.altKey ||
2598 // event.metaKey ? ', ' +
2599 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2600 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2603 if (typeof event.charCode != 'undefined') {
2604 // non-IE keypress events have a translated charCode value. Also, our
2605 // fake events generated when receiving keydown events include this data
2607 ch = event.charCode;
2608 key = event.keyCode;
2610 // When sending a keypress event, IE includes the translated character
2611 // code in the keyCode field.
2616 // Apply modifier keys (ctrl and shift)
2620 ch = this.applyModifiers(ch, event);
2622 // By this point, "ch" is either defined and contains the character code, or
2623 // it is undefined and "key" defines the code of a function key
2624 if (ch != undefined) {
2625 this.scrollable.scrollTop = this.numScrollbackLines *
2626 this.cursorHeight + 1;
2628 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2629 // Many programs have difficulties dealing with parametrized escape
2630 // sequences for function keys. Thus, if ALT is the only modifier
2631 // key, return Emacs-style keycodes for commonly used keys.
2633 case 33: /* Page Up */ ch = '\u001B<'; break;
2634 case 34: /* Page Down */ ch = '\u001B>'; break;
2635 case 37: /* Left */ ch = '\u001Bb'; break;
2636 case 38: /* Up */ ch = '\u001Bp'; break;
2637 case 39: /* Right */ ch = '\u001Bf'; break;
2638 case 40: /* Down */ ch = '\u001Bn'; break;
2639 case 46: /* Delete */ ch = '\u001Bd'; break;
2642 } else if (event.shiftKey && !event.ctrlKey &&
2643 !event.altKey && !event.metaKey) {
2645 case 33: /* Page Up */ this.scrollBack(); return;
2646 case 34: /* Page Down */ this.scrollFore(); return;
2650 if (ch == undefined) {
2652 case 8: /* Backspace */ ch = '\u007f'; break;
2653 case 9: /* Tab */ ch = '\u0009'; break;
2654 case 10: /* Return */ ch = '\u000A'; break;
2655 case 13: /* Enter */ ch = this.crLfMode ?
2656 '\r\n' : '\r'; break;
2657 case 16: /* Shift */ return;
2658 case 17: /* Ctrl */ return;
2659 case 18: /* Alt */ return;
2660 case 19: /* Break */ return;
2661 case 20: /* Caps Lock */ return;
2662 case 27: /* Escape */ ch = '\u001B'; break;
2663 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2664 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2665 case 35: /* End */ ch = '\u001BOF'; break;
2666 case 36: /* Home */ ch = '\u001BOH'; break;
2667 case 37: /* Left */ ch = this.cursorKeyMode ?
2668 '\u001BOD' : '\u001B[D'; break;
2669 case 38: /* Up */ ch = this.cursorKeyMode ?
2670 '\u001BOA' : '\u001B[A'; break;
2671 case 39: /* Right */ ch = this.cursorKeyMode ?
2672 '\u001BOC' : '\u001B[C'; break;
2673 case 40: /* Down */ ch = this.cursorKeyMode ?
2674 '\u001BOB' : '\u001B[B'; break;
2675 case 45: /* Insert */ ch = '\u001B[2~'; break;
2676 case 46: /* Delete */ ch = '\u001B[3~'; break;
2677 case 91: /* Left Window */ return;
2678 case 92: /* Right Window */ return;
2679 case 93: /* Select */ return;
2680 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2681 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2682 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2683 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2684 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2685 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2686 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2687 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2688 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2689 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2690 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2691 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2692 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2693 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2694 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2695 case 112: /* F1 */ ch = '\u001BOP'; break;
2696 case 113: /* F2 */ ch = '\u001BOQ'; break;
2697 case 114: /* F3 */ ch = '\u001BOR'; break;
2698 case 115: /* F4 */ ch = '\u001BOS'; break;
2699 case 116: /* F5 */ ch = '\u001B[15~'; break;
2700 case 117: /* F6 */ ch = '\u001B[17~'; break;
2701 case 118: /* F7 */ ch = '\u001B[18~'; break;
2702 case 119: /* F8 */ ch = '\u001B[19~'; break;
2703 case 120: /* F9 */ ch = '\u001B[20~'; break;
2704 case 121: /* F10 */ ch = '\u001B[21~'; break;
2705 case 122: /* F11 */ ch = '\u001B[23~'; break;
2706 case 123: /* F12 */ ch = '\u001B[24~'; break;
2707 case 144: /* Num Lock */ return;
2708 case 145: /* Scroll Lock */ return;
2709 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2710 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2711 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2712 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2713 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2714 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2715 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2716 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2717 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2718 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2719 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2722 this.scrollable.scrollTop = this.numScrollbackLines *
2723 this.cursorHeight + 1;
2727 // "ch" now contains the sequence of keycodes to send. But we might still
2728 // have to apply the effects of modifier keys.
2729 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2730 var start, digit, part1, part2;
2731 if ((start = ch.substr(0, 2)) == '\u001B[') {
2733 part1.length < ch.length &&
2734 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2735 part1 = ch.substr(0, part1.length + 1);
2737 part2 = ch.substr(part1.length);
2738 if (part1.length > 2) {
2741 } else if (start == '\u001BO') {
2743 part2 = ch.substr(2);
2745 if (part1 != undefined) {
2747 ((event.shiftKey ? 1 : 0) +
2748 (event.altKey|event.metaKey ? 2 : 0) +
2749 (event.ctrlKey ? 4 : 0)) +
2751 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2756 if (this.menu.style.visibility == 'hidden') {
2757 // this.vt100('R: c=');
2758 // for (var i = 0; i < ch.length; i++)
2759 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2760 // this.vt100('\r\n');
2761 this.keysPressed(ch);
2765 VT100.prototype.inspect = function(o, d) {
2766 if (d == undefined) {
2770 if (typeof o == 'object' && ++d < 2) {
2773 rc += this.spaces(d * 2) + i + ' -> ';
2775 rc += this.inspect(o[i], d);
2777 rc += '?' + '?' + '?\r\n';
2782 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2787 VT100.prototype.checkComposedKeys = function(event) {
2788 // Composed keys (at least on Linux) do not generate normal events.
2789 // Instead, they get entered into the text field. We normally catch
2790 // this on the next keyup event.
2791 var s = this.input.value;
2793 this.input.value = '';
2794 if (this.menu.style.visibility == 'hidden') {
2795 this.keysPressed(s);
2800 VT100.prototype.fixEvent = function(event) {
2801 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2802 // is used as a second-level selector, clear the modifier bits before
2803 // handling the event.
2804 if (event.ctrlKey && event.altKey) {
2806 fake.charCode = event.charCode;
2807 fake.keyCode = event.keyCode;
2808 fake.ctrlKey = false;
2809 fake.shiftKey = event.shiftKey;
2810 fake.altKey = false;
2811 fake.metaKey = event.metaKey;
2815 // Some browsers fail to translate keys, if both shift and alt/meta is
2816 // pressed at the same time. We try to translate those cases, but that
2817 // only works for US keyboard layouts.
2818 if (event.shiftKey) {
2821 switch (this.lastNormalKeyDownEvent.keyCode) {
2822 case 39: /* ' -> " */ u = 39; s = 34; break;
2823 case 44: /* , -> < */ u = 44; s = 60; break;
2824 case 45: /* - -> _ */ u = 45; s = 95; break;
2825 case 46: /* . -> > */ u = 46; s = 62; break;
2826 case 47: /* / -> ? */ u = 47; s = 63; break;
2828 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2829 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2830 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2831 case 51: /* 3 -> # */ u = 51; s = 35; break;
2832 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2833 case 53: /* 5 -> % */ u = 53; s = 37; break;
2834 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2835 case 55: /* 7 -> & */ u = 55; s = 38; break;
2836 case 56: /* 8 -> * */ u = 56; s = 42; break;
2837 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2839 case 59: /* ; -> : */ u = 59; s = 58; break;
2840 case 61: /* = -> + */ u = 61; s = 43; break;
2841 case 91: /* [ -> { */ u = 91; s = 123; break;
2842 case 92: /* \ -> | */ u = 92; s = 124; break;
2843 case 93: /* ] -> } */ u = 93; s = 125; break;
2844 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2846 case 109: /* - -> _ */ u = 45; s = 95; break;
2847 case 111: /* / -> ? */ u = 47; s = 63; break;
2849 case 186: /* ; -> : */ u = 59; s = 58; break;
2850 case 187: /* = -> + */ u = 61; s = 43; break;
2851 case 188: /* , -> < */ u = 44; s = 60; break;
2852 case 189: /* - -> _ */ u = 45; s = 95; break;
2853 case 190: /* . -> > */ u = 46; s = 62; break;
2854 case 191: /* / -> ? */ u = 47; s = 63; break;
2855 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2856 case 219: /* [ -> { */ u = 91; s = 123; break;
2857 case 220: /* \ -> | */ u = 92; s = 124; break;
2858 case 221: /* ] -> } */ u = 93; s = 125; break;
2859 case 222: /* ' -> " */ u = 39; s = 34; break;
2862 if (s && (event.charCode == u || event.charCode == 0)) {
2865 fake.keyCode = event.keyCode;
2866 fake.ctrlKey = event.ctrlKey;
2867 fake.shiftKey = event.shiftKey;
2868 fake.altKey = event.altKey;
2869 fake.metaKey = event.metaKey;
2876 VT100.prototype.keyDown = function(event) {
2877 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2878 // (event.shiftKey || event.ctrlKey || event.altKey ||
2879 // event.metaKey ? ', ' +
2880 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2881 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2884 // Keep the cursor lit when there is user activity.
2885 this.animateCursor('bright');
2887 this.checkComposedKeys(event);
2888 this.lastKeyPressedEvent = undefined;
2889 this.lastKeyDownEvent = undefined;
2890 this.lastNormalKeyDownEvent = event;
2893 event.keyCode == 32 ||
2894 event.keyCode >= 48 && event.keyCode <= 57 ||
2895 event.keyCode >= 65 && event.keyCode <= 90;
2898 event.keyCode >= 96 && event.keyCode <= 105 ||
2899 event.keyCode == 226;
2902 event.keyCode == 59 || event.keyCode == 61 ||
2903 event.keyCode == 106 || event.keyCode == 107 ||
2904 event.keyCode >= 109 && event.keyCode <= 111 ||
2905 event.keyCode >= 186 && event.keyCode <= 192 ||
2906 event.keyCode >= 219 && event.keyCode <= 223 ||
2907 event.keyCode == 252;
2909 if (navigator.appName == 'Konqueror') {
2910 normalKey |= event.keyCode < 128;
2915 // We normally prefer to look at keypress events, as they perform the
2916 // translation from keyCode to charCode. This is important, as the
2917 // translation is locale-dependent.
2918 // But for some keys, we must intercept them during the keydown event,
2919 // as they would otherwise get interpreted by the browser.
2920 // Even, when doing all of this, there are some keys that we can never
2921 // intercept. This applies to some of the menu navigation keys in IE.
2922 // In fact, we see them, but we cannot stop IE from seeing them, too.
2923 if ((event.charCode || event.keyCode) &&
2924 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2926 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2927 // interpret this sequence ourselves, as some keyboard layouts use
2928 // it for second-level layouts.
2929 !(event.ctrlKey && event.altKey)) ||
2930 this.catchModifiersEarly && normalKey && !alphNumKey &&
2931 (event.ctrlKey || event.altKey || event.metaKey) ||
2933 this.lastKeyDownEvent = event;
2935 fake.ctrlKey = event.ctrlKey;
2936 fake.shiftKey = event.shiftKey;
2937 fake.altKey = event.altKey;
2938 fake.metaKey = event.metaKey;
2940 fake.charCode = event.keyCode;
2944 fake.keyCode = event.keyCode;
2945 if (!alphNumKey && event.shiftKey) {
2946 fake = this.fixEvent(fake);
2950 this.handleKey(fake);
2951 this.lastNormalKeyDownEvent = undefined;
2954 // For non-IE browsers
2955 event.stopPropagation();
2956 event.preventDefault();
2961 event.cancelBubble = true;
2962 event.returnValue = false;
2972 VT100.prototype.keyPressed = function(event) {
2973 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2974 // (event.shiftKey || event.ctrlKey || event.altKey ||
2975 // event.metaKey ? ', ' +
2976 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2977 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2980 // Keep the cursor lit when there is user activity.
2981 this.animateCursor('bright');
2983 if (this.lastKeyDownEvent) {
2984 // If we already processed the key on keydown, do not process it
2985 // again here. Ideally, the browser should not even have generated a
2986 // keypress event in this case. But that does not appear to always work.
2987 this.lastKeyDownEvent = undefined;
2989 this.handleKey(event.altKey || event.metaKey
2990 ? this.fixEvent(event) : event);
2994 // For non-IE browsers
2995 event.preventDefault();
3001 event.cancelBubble = true;
3002 event.returnValue = false;
3007 this.lastNormalKeyDownEvent = undefined;
3008 this.lastKeyPressedEvent = event;
3012 VT100.prototype.keyUp = function(event) {
3013 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3014 // (event.shiftKey || event.ctrlKey || event.altKey ||
3015 // event.metaKey ? ', ' +
3016 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3017 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3020 // Keep the cursor lit when there is user activity.
3021 this.animateCursor('bright');
3023 if (this.lastKeyPressedEvent) {
3024 // The compose key on Linux occasionally confuses the browser and keeps
3025 // inserting bogus characters into the input field, even if just a regular
3026 // key has been pressed. Detect this case and drop the bogus characters.
3028 event.srcElement).value = '';
3030 // This is usually were we notice that a key has been composed and
3031 // thus failed to generate normal events.
3032 this.checkComposedKeys(event);
3034 // Some browsers don't report keypress events if ctrl or alt is pressed
3035 // for non-alphanumerical keys. Patch things up for now, but in the
3036 // future we will catch these keys earlier (in the keydown handler).
3037 if (this.lastNormalKeyDownEvent) {
3038 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3039 this.catchModifiersEarly = true;
3041 event.keyCode == 32 ||
3042 event.keyCode >= 48 && event.keyCode <= 57 ||
3043 event.keyCode >= 65 && event.keyCode <= 90;
3046 event.keyCode >= 96 && event.keyCode <= 105;
3049 event.keyCode == 59 || event.keyCode == 61 ||
3050 event.keyCode == 106 || event.keyCode == 107 ||
3051 event.keyCode >= 109 && event.keyCode <= 111 ||
3052 event.keyCode >= 186 && event.keyCode <= 192 ||
3053 event.keyCode >= 219 && event.keyCode <= 223 ||
3054 event.keyCode == 252;
3056 fake.ctrlKey = event.ctrlKey;
3057 fake.shiftKey = event.shiftKey;
3058 fake.altKey = event.altKey;
3059 fake.metaKey = event.metaKey;
3061 fake.charCode = event.keyCode;
3065 fake.keyCode = event.keyCode;
3066 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3067 fake = this.fixEvent(fake);
3070 this.lastNormalKeyDownEvent = undefined;
3071 this.handleKey(fake);
3077 event.cancelBubble = true;
3078 event.returnValue = false;
3083 this.lastKeyDownEvent = undefined;
3084 this.lastKeyPressedEvent = undefined;
3088 VT100.prototype.animateCursor = function(state) {
3089 if (state != undefined) {
3090 this.cursor.className = state;
3092 // Reset the blink timer when a state is explicitly defined.
3093 if (this.cursorInterval) {
3094 clearInterval(this.cursorInterval);
3095 this.cursorInterval = undefined;
3098 if (this.cursor.className != 'inactive') {
3099 if (this.blinkingCursor && this.cursor.className == 'bright') {
3100 this.cursor.className = 'dim';
3102 this.cursor.className = 'bright';
3107 if (!this.cursorInterval) {
3108 this.cursorInterval = setInterval(
3111 vt100.animateCursor();
3113 // Use this opportunity to check whether the user entered a composed
3114 // key, or whether somebody pasted text into the textfield.
3115 vt100.checkComposedKeys();
3121 VT100.prototype.blurCursor = function() {
3122 this.animateCursor('inactive');
3125 VT100.prototype.focusCursor = function() {
3126 this.animateCursor('bright');
3129 VT100.prototype.flashScreen = function() {
3130 this.isInverted = !this.isInverted;
3131 this.refreshInvertedState();
3132 this.isInverted = !this.isInverted;
3133 setTimeout(function(vt100) {
3135 vt100.refreshInvertedState();
3140 VT100.prototype.beep = function() {
3141 if (this.visualBell) {
3148 this.beeper.src = 'beep.wav';
3155 VT100.prototype.bs = function() {
3156 if (this.cursorX > 0) {
3157 this.gotoXY(this.cursorX - 1, this.cursorY);
3158 this.needWrap = false;
3162 VT100.prototype.ht = function(count) {
3163 if (count == undefined) {
3166 var cx = this.cursorX;
3167 while (count-- > 0) {
3168 while (cx++ < this.terminalWidth) {
3169 var tabState = this.userTabStop[cx];
3170 if (tabState == false) {
3171 // Explicitly cleared tab stop
3173 } else if (tabState) {
3174 // Explicitly set tab stop
3177 // Default tab stop at each eighth column
3184 if (cx > this.terminalWidth - 1) {
3185 cx = this.terminalWidth - 1;
3187 if (cx != this.cursorX) {
3188 this.gotoXY(cx, this.cursorY);
3192 VT100.prototype.rt = function(count) {
3193 if (count == undefined) {
3196 var cx = this.cursorX;
3197 while (count-- > 0) {
3199 var tabState = this.userTabStop[cx];
3200 if (tabState == false) {
3201 // Explicitly cleared tab stop
3203 } else if (tabState) {
3204 // Explicitly set tab stop
3207 // Default tab stop at each eighth column
3217 if (cx != this.cursorX) {
3218 this.gotoXY(cx, this.cursorY);
3222 VT100.prototype.cr = function() {
3223 this.gotoXY(0, this.cursorY);
3224 this.needWrap = false;
3227 VT100.prototype.lf = function(count) {
3228 if (count == undefined) {
3231 if (count > this.terminalHeight) {
3232 count = this.terminalHeight;
3238 while (count-- > 0) {
3239 if (this.cursorY == this.bottom - 1) {
3240 this.scrollRegion(0, this.top + 1,
3241 this.terminalWidth, this.bottom - this.top - 1,
3242 0, -1, this.color, this.style);
3244 } else if (this.cursorY < this.terminalHeight - 1) {
3245 this.gotoXY(this.cursorX, this.cursorY + 1);
3250 VT100.prototype.ri = function(count) {
3251 if (count == undefined) {
3254 if (count > this.terminalHeight) {
3255 count = this.terminalHeight;
3261 while (count-- > 0) {
3262 if (this.cursorY == this.top) {
3263 this.scrollRegion(0, this.top,
3264 this.terminalWidth, this.bottom - this.top - 1,
3265 0, 1, this.color, this.style);
3266 } else if (this.cursorY > 0) {
3267 this.gotoXY(this.cursorX, this.cursorY - 1);
3270 this.needWrap = false;
3273 VT100.prototype.respondID = function() {
3274 this.respondString += '\u001B[?6c';
3277 VT100.prototype.respondSecondaryDA = function() {
3278 this.respondString += '\u001B[>0;0;0c';
3282 VT100.prototype.updateStyle = function() {
3284 if (this.attr & ATTR_UNDERLINE) {
3285 this.style = 'text-decoration: underline;';
3287 var bg = (this.attr >> 4) & 0xF;
3288 var fg = this.attr & 0xF;
3289 if (this.attr & ATTR_REVERSE) {
3294 if ((this.attr & (ATTR_REVERSE | ATTR_DIM)) == ATTR_DIM) {
3295 fg = 8; // Dark grey
3296 } else if (this.attr & ATTR_BRIGHT) {
3298 this.style = 'font-weight: bold;';
3300 if (this.attr & ATTR_BLINK) {
3301 this.style = 'text-decoration: blink;';
3303 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3306 VT100.prototype.setAttrColors = function(attr) {
3307 if (attr != this.attr) {
3313 VT100.prototype.saveCursor = function() {
3314 this.savedX[this.currentScreen] = this.cursorX;
3315 this.savedY[this.currentScreen] = this.cursorY;
3316 this.savedAttr[this.currentScreen] = this.attr;
3317 this.savedUseGMap = this.useGMap;
3318 for (var i = 0; i < 4; i++) {
3319 this.savedGMap[i] = this.GMap[i];
3321 this.savedValid[this.currentScreen] = true;
3324 VT100.prototype.restoreCursor = function() {
3325 if (!this.savedValid[this.currentScreen]) {
3328 this.attr = this.savedAttr[this.currentScreen];
3330 this.useGMap = this.savedUseGMap;
3331 for (var i = 0; i < 4; i++) {
3332 this.GMap[i] = this.savedGMap[i];
3334 this.translate = this.GMap[this.useGMap];
3335 this.needWrap = false;
3336 this.gotoXY(this.savedX[this.currentScreen],
3337 this.savedY[this.currentScreen]);
3340 VT100.prototype.getTransformName = function() {
3341 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3342 for (var i = 0; i < styles.length; ++i) {
3343 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3350 VT100.prototype.getTransformStyle = function(transform, scale) {
3351 return scale && scale != 1.0
3352 ? transform == 'filter'
3353 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3354 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3355 "sizingMethod='auto expand')"
3356 : 'translateX(-50%) ' +
3357 'scaleX(' + (1.0/scale) + ') ' +
3362 VT100.prototype.set80_132Mode = function(state) {
3363 var transform = this.getTransformName();
3365 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3369 this.getTransformStyle(transform, 1.65):'';
3370 this.console[this.currentScreen].style[transform] = style;
3371 this.cursor.style[transform] = style;
3372 this.space.style[transform] = style;
3373 this.scale = state ? 1.65 : 1.0;
3374 if (transform == 'filter') {
3375 this.console[this.currentScreen].style.width = state ? '165%' : '';
3381 VT100.prototype.setMode = function(state) {
3382 for (var i = 0; i <= this.npar; i++) {
3383 if (this.isQuestionMark) {
3384 switch (this.par[i]) {
3385 case 1: this.cursorKeyMode = state; break;
3386 case 3: this.set80_132Mode(state); break;
3387 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3388 case 6: this.offsetMode = state; break;
3389 case 7: this.autoWrapMode = state; break;
3391 case 9: this.mouseReporting = state; break;
3392 case 25: this.cursorNeedsShowing = state;
3393 if (state) { this.showCursor(); }
3394 else { this.hideCursor(); } break;
3397 case 47: this.enableAlternateScreen(state); break;
3401 switch (this.par[i]) {
3402 case 3: this.dispCtrl = state; break;
3403 case 4: this.insertMode = state; break;
3404 case 20:this.crLfMode = state; break;
3411 VT100.prototype.statusReport = function() {
3412 // Ready and operational.
3413 this.respondString += '\u001B[0n';
3416 VT100.prototype.cursorReport = function() {
3417 this.respondString += '\u001B[' +
3418 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3420 (this.cursorX + 1) +
3424 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3425 // Changing of cursor color is not implemented.
3428 VT100.prototype.openPrinterWindow = function() {
3431 if (!this.printWin || this.printWin.closed) {
3432 this.printWin = window.open('', 'print-output',
3433 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3434 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3435 this.printWin.document.body.innerHTML =
3436 '<link rel="stylesheet" href="' +
3437 document.location.protocol + '//' + document.location.host +
3438 document.location.pathname.replace(/[^/]*$/, '') +
3439 'print-styles.css" type="text/css">\n' +
3440 '<div id="options"><input id="autoprint" type="checkbox"' +
3441 (this.autoprint ? ' checked' : '') + '>' +
3442 'Automatically, print page(s) when job is ready' +
3443 '</input></div>\n' +
3444 '<div id="spacer"><input type="checkbox"> </input></div>' +
3445 '<pre id="print"></pre>\n';
3446 var autoprint = this.printWin.document.getElementById('autoprint');
3447 this.addListener(autoprint, 'click',
3448 (function(vt100, autoprint) {
3450 vt100.autoprint = autoprint.checked;
3451 vt100.storeUserSettings();
3454 })(this, autoprint));
3455 this.printWin.document.title = 'ShellInABox Printer Output';
3458 // Maybe, a popup blocker prevented us from working. Better catch the
3459 // exception, so that we won't break the entire terminal session. The
3460 // user probably needs to disable the blocker first before retrying the
3464 rc &= this.printWin && !this.printWin.closed &&
3465 (this.printWin.innerWidth ||
3466 this.printWin.document.documentElement.clientWidth ||
3467 this.printWin.document.body.clientWidth) > 1;
3469 if (!rc && this.printing == 100) {
3470 // Different popup blockers work differently. We try to detect a couple
3471 // of common methods. And then we retry again a brief amount later, as
3472 // false positives are otherwise possible. If we are sure that there is
3473 // a popup blocker in effect, we alert the user to it. This is helpful
3474 // as some popup blockers have minimal or no UI, and the user might not
3475 // notice that they are missing the popup. In any case, we only show at
3476 // most one message per print job.
3477 this.printing = true;
3478 setTimeout((function(win) {
3480 if (!win || win.closed ||
3482 win.document.documentElement.clientWidth ||
3483 win.document.body.clientWidth) <= 1) {
3484 alert('Attempted to print, but a popup blocker ' +
3485 'prevented the printer window from opening');
3488 })(this.printWin), 2000);
3493 VT100.prototype.sendToPrinter = function(s) {
3494 this.openPrinterWindow();
3496 var doc = this.printWin.document;
3497 var print = doc.getElementById('print');
3498 if (print.lastChild && print.lastChild.nodeName == '#text') {
3499 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3501 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3504 // There probably was a more aggressive popup blocker that prevented us
3505 // from accessing the printer windows.
3509 VT100.prototype.sendControlToPrinter = function(ch) {
3510 // We get called whenever doControl() is active. But for the printer, we
3511 // only implement a basic line printer that doesn't understand most of
3512 // the escape sequences of the VT100 terminal. In fact, the only escape
3513 // sequence that we really need to recognize is '^[[5i' for turning the
3519 this.openPrinterWindow();
3520 var doc = this.printWin.document;
3521 var print = doc.getElementById('print');
3522 var chars = print.lastChild &&
3523 print.lastChild.nodeName == '#text' ?
3524 print.lastChild.textContent.length : 0;
3525 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3532 this.openPrinterWindow();
3533 var pageBreak = this.printWin.document.createElement('div');
3534 pageBreak.className = 'pagebreak';
3535 pageBreak.innerHTML = '<hr />';
3536 this.printWin.document.getElementById('print').appendChild(pageBreak);
3540 this.openPrinterWindow();
3541 var lineBreak = this.printWin.document.createElement('br');
3542 this.printWin.document.getElementById('print').appendChild(lineBreak);
3549 switch (this.isEsc) {
3551 this.isEsc = ESnormal;
3554 this.isEsc = ESsquare;
3562 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3563 0, 0, 0, 0, 0, 0, 0, 0 ];
3564 this.isEsc = ESgetpars;
3565 this.isQuestionMark = ch == 0x3F /*?*/;
3566 if (this.isQuestionMark) {
3571 if (ch == 0x3B /*;*/) {
3574 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3575 var par = this.par[this.npar];
3576 if (par == undefined) {
3579 this.par[this.npar] = 10*par + (ch & 0xF);
3582 this.isEsc = ESgotpars;
3586 this.isEsc = ESnormal;
3587 if (this.isQuestionMark) {
3592 this.csii(this.par[0]);
3599 this.isEsc = ESnormal;
3605 // There probably was a more aggressive popup blocker that prevented us
3606 // from accessing the printer windows.
3610 VT100.prototype.csiAt = function(number) {
3615 if (number > this.terminalWidth - this.cursorX) {
3616 number = this.terminalWidth - this.cursorX;
3618 this.scrollRegion(this.cursorX, this.cursorY,
3619 this.terminalWidth - this.cursorX - number, 1,
3620 number, 0, this.color, this.style);
3621 this.needWrap = false;
3624 VT100.prototype.csii = function(number) {
3627 case 0: // Print Screen
3630 case 4: // Stop printing
3632 if (this.printing && this.printWin && !this.printWin.closed) {
3633 var print = this.printWin.document.getElementById('print');
3634 while (print.lastChild &&
3635 print.lastChild.tagName == 'DIV' &&
3636 print.lastChild.className == 'pagebreak') {
3637 // Remove trailing blank pages
3638 print.removeChild(print.lastChild);
3640 if (this.autoprint) {
3641 this.printWin.print();
3646 this.printing = false;
3648 case 5: // Start printing
3649 if (!this.printing && this.printWin && !this.printWin.closed) {
3650 this.printWin.document.getElementById('print').innerHTML = '';
3652 this.printing = 100;
3659 VT100.prototype.csiJ = function(number) {
3661 case 0: // Erase from cursor to end of display
3662 this.clearRegion(this.cursorX, this.cursorY,
3663 this.terminalWidth - this.cursorX, 1,
3664 this.color, this.style);
3665 if (this.cursorY < this.terminalHeight-2) {
3666 this.clearRegion(0, this.cursorY+1,
3667 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3668 this.color, this.style);
3671 case 1: // Erase from start to cursor
3672 if (this.cursorY > 0) {
3673 this.clearRegion(0, 0,
3674 this.terminalWidth, this.cursorY,
3675 this.color, this.style);
3677 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3678 this.color, this.style);
3680 case 2: // Erase whole display
3681 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3682 this.color, this.style);
3690 VT100.prototype.csiK = function(number) {
3692 case 0: // Erase from cursor to end of line
3693 this.clearRegion(this.cursorX, this.cursorY,
3694 this.terminalWidth - this.cursorX, 1,
3695 this.color, this.style);
3697 case 1: // Erase from start of line to cursor
3698 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3699 this.color, this.style);
3701 case 2: // Erase whole line
3702 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3703 this.color, this.style);
3711 VT100.prototype.csiL = function(number) {
3712 // Open line by inserting blank line(s)
3713 if (this.cursorY >= this.bottom) {
3719 if (number > this.bottom - this.cursorY) {
3720 number = this.bottom - this.cursorY;
3722 this.scrollRegion(0, this.cursorY,
3723 this.terminalWidth, this.bottom - this.cursorY - number,
3724 0, number, this.color, this.style);
3728 VT100.prototype.csiM = function(number) {
3729 // Delete line(s), scrolling up the bottom of the screen.
3730 if (this.cursorY >= this.bottom) {
3736 if (number > this.bottom - this.cursorY) {
3737 number = bottom - cursorY;
3739 this.scrollRegion(0, this.cursorY + number,
3740 this.terminalWidth, this.bottom - this.cursorY - number,
3741 0, -number, this.color, this.style);
3745 VT100.prototype.csim = function() {
3746 for (var i = 0; i <= this.npar; i++) {
3747 switch (this.par[i]) {
3748 case 0: this.attr = ATTR_DEFAULT; break;
3749 case 1: this.attr = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT; break;
3750 case 2: this.attr = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM; break;
3751 case 4: this.attr |= ATTR_UNDERLINE; break;
3752 case 5: this.attr |= ATTR_BLINK; break;
3753 case 7: this.attr |= ATTR_REVERSE; break;
3755 this.translate = this.GMap[this.useGMap];
3756 this.dispCtrl = false;
3757 this.toggleMeta = false;
3760 this.translate = this.CodePage437Map;
3761 this.dispCtrl = true;
3762 this.toggleMeta = false;
3765 this.translate = this.CodePage437Map;
3766 this.dispCtrl = true;
3767 this.toggleMeta = true;
3770 case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM); break;
3771 case 24: this.attr &= ~ ATTR_UNDERLINE; break;
3772 case 25: this.attr &= ~ ATTR_BLINK; break;
3773 case 27: this.attr &= ~ ATTR_REVERSE; break;
3774 case 38: this.attr = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|
3775 ATTR_UNDERLINE; break;
3776 case 39: this.attr &= ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F); break;
3777 case 49: this.attr |= 0xF0; break;
3779 if (this.par[i] >= 30 && this.par[i] <= 37) {
3780 var fg = this.par[i] - 30;
3781 this.attr = (this.attr & ~0x0F) | fg;
3782 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3783 var bg = this.par[i] - 40;
3784 this.attr = (this.attr & ~0xF0) | (bg << 4);
3792 VT100.prototype.csiP = function(number) {
3793 // Delete character(s) following cursor
3797 if (number > this.terminalWidth - this.cursorX) {
3798 number = this.terminalWidth - this.cursorX;
3800 this.scrollRegion(this.cursorX + number, this.cursorY,
3801 this.terminalWidth - this.cursorX - number, 1,
3802 -number, 0, this.color, this.style);
3806 VT100.prototype.csiX = function(number) {
3807 // Clear characters following cursor
3811 if (number > this.terminalWidth - this.cursorX) {
3812 number = this.terminalWidth - this.cursorX;
3814 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3815 this.color, this.style);
3819 VT100.prototype.settermCommand = function() {
3820 // Setterm commands are not implemented
3823 VT100.prototype.doControl = function(ch) {
3824 if (this.printing) {
3825 this.sendControlToPrinter(ch);
3830 case 0x00: /* ignored */ break;
3831 case 0x08: this.bs(); break;
3832 case 0x09: this.ht(); break;
3836 case 0x84: this.lf(); if (!this.crLfMode) break;
3837 case 0x0D: this.cr(); break;
3838 case 0x85: this.cr(); this.lf(); break;
3839 case 0x0E: this.useGMap = 1;
3840 this.translate = this.GMap[1];
3841 this.dispCtrl = true; break;
3842 case 0x0F: this.useGMap = 0;
3843 this.translate = this.GMap[0];
3844 this.dispCtrl = false; break;
3846 case 0x1A: this.isEsc = ESnormal; break;
3847 case 0x1B: this.isEsc = ESesc; break;
3848 case 0x7F: /* ignored */ break;
3849 case 0x88: this.userTabStop[this.cursorX] = true; break;
3850 case 0x8D: this.ri(); break;
3851 case 0x8E: this.isEsc = ESss2; break;
3852 case 0x8F: this.isEsc = ESss3; break;
3853 case 0x9A: this.respondID(); break;
3854 case 0x9B: this.isEsc = ESsquare; break;
3855 case 0x07: if (this.isEsc != EStitle) {
3859 default: switch (this.isEsc) {
3861 this.isEsc = ESnormal;
3863 /*%*/ case 0x25: this.isEsc = ESpercent; break;
3864 /*(*/ case 0x28: this.isEsc = ESsetG0; break;
3866 /*)*/ case 0x29: this.isEsc = ESsetG1; break;
3868 /***/ case 0x2A: this.isEsc = ESsetG2; break;
3870 /*+*/ case 0x2B: this.isEsc = ESsetG3; break;
3871 /*#*/ case 0x23: this.isEsc = EShash; break;
3872 /*7*/ case 0x37: this.saveCursor(); break;
3873 /*8*/ case 0x38: this.restoreCursor(); break;
3874 /*>*/ case 0x3E: this.applKeyMode = false; break;
3875 /*=*/ case 0x3D: this.applKeyMode = true; break;
3876 /*D*/ case 0x44: this.lf(); break;
3877 /*E*/ case 0x45: this.cr(); this.lf(); break;
3878 /*M*/ case 0x4D: this.ri(); break;
3879 /*N*/ case 0x4E: this.isEsc = ESss2; break;
3880 /*O*/ case 0x4F: this.isEsc = ESss3; break;
3881 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3882 /*Z*/ case 0x5A: this.respondID(); break;
3883 /*[*/ case 0x5B: this.isEsc = ESsquare; break;
3884 /*]*/ case 0x5D: this.isEsc = ESnonstd; break;
3885 /*c*/ case 0x63: this.reset(); break;
3886 /*g*/ case 0x67: this.flashScreen(); break;
3894 /*2*/ case 0x32: this.isEsc = EStitle; this.titleString = ''; break;
3895 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3896 this.isEsc = ESpalette; break;
3897 /*R*/ case 0x52: // Palette support is not implemented
3898 this.isEsc = ESnormal; break;
3899 default: this.isEsc = ESnormal; break;
3903 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3904 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3905 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3906 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3908 if (this.npar == 7) {
3909 // Palette support is not implemented
3910 this.isEsc = ESnormal;
3913 this.isEsc = ESnormal;
3918 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3919 0, 0, 0, 0, 0, 0, 0, 0 ];
3920 this.isEsc = ESgetpars;
3921 /*[*/ if (ch == 0x5B) { // Function key
3922 this.isEsc = ESfunckey;
3925 /*?*/ this.isQuestionMark = ch == 0x3F;
3926 if (this.isQuestionMark) {
3933 /*;*/ if (ch == 0x3B) {
3936 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3937 var par = this.par[this.npar];
3938 if (par == undefined) {
3941 this.par[this.npar] = 10*par + (ch & 0xF);
3943 } else if (this.isEsc == ESdeviceattr) {
3945 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3946 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3947 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3948 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3951 this.isEsc = ESnormal;
3954 this.isEsc = ESgotpars;
3958 this.isEsc = ESnormal;
3959 if (this.isQuestionMark) {
3961 /*h*/ case 0x68: this.setMode(true); break;
3962 /*l*/ case 0x6C: this.setMode(false); break;
3963 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3966 this.isQuestionMark = false;
3970 /*!*/ case 0x21: this.isEsc = ESbang; break;
3971 /*>*/ case 0x3E: if (!this.npar) this.isEsc = ESdeviceattr; break;
3973 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3974 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3975 this.cursorY - (this.par[0] ? this.par[0] : 1));
3978 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3979 this.cursorY + (this.par[0] ? this.par[0] : 1));
3982 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3983 this.cursorY); break;
3984 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3985 this.cursorY); break;
3986 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3988 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3990 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3992 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3993 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3994 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3995 /*i*/ case 0x69: this.csii(this.par[0]); break;
3996 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3997 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3998 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3999 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
4000 /*m*/ case 0x6D: this.csim(); break;
4001 /*P*/ case 0x50: this.csiP(this.par[0]); break;
4002 /*X*/ case 0x58: this.csiX(this.par[0]); break;
4003 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4004 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4005 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4006 /*g*/ case 0x67: if (this.par[0] == 0) {
4007 this.userTabStop[this.cursorX] = false;
4008 } else if (this.par[0] == 2 || this.par[0] == 3) {
4009 this.userTabStop = [ ];
4010 for (var i = 0; i < this.terminalWidth; i++) {
4011 this.userTabStop[i] = false;
4015 /*h*/ case 0x68: this.setMode(true); break;
4016 /*l*/ case 0x6C: this.setMode(false); break;
4017 /*n*/ case 0x6E: switch (this.par[0]) {
4018 case 5: this.statusReport(); break;
4019 case 6: this.cursorReport(); break;
4023 /*q*/ case 0x71: // LED control not implemented
4025 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4026 var b = this.par[1] ? this.par[1]
4027 : this.terminalHeight;
4028 if (t < b && b <= this.terminalHeight) {
4034 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4035 if (c > this.terminalWidth * this.terminalHeight) {
4036 c = this.terminalWidth * this.terminalHeight;
4039 lineBuf += this.lastCharacter;
4042 /*s*/ case 0x73: this.saveCursor(); break;
4043 /*u*/ case 0x75: this.restoreCursor(); break;
4044 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4045 /*]*/ case 0x5D: this.settermCommand(); break;
4053 this.isEsc = ESnormal;
4056 this.isEsc = ESnormal;
4058 /*@*/ case 0x40: this.utfEnabled = false; break;
4060 /*8*/ case 0x38: this.utfEnabled = true; break;
4065 this.isEsc = ESnormal; break;
4067 this.isEsc = ESnormal;
4068 /*8*/ if (ch == 0x38) {
4069 // Screen alignment test not implemented
4076 var g = this.isEsc - ESsetG0;
4077 this.isEsc = ESnormal;
4079 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4081 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4082 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4083 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4086 if (this.useGMap == g) {
4087 this.translate = this.GMap[g];
4092 if (this.titleString && this.titleString.charAt(0) == ';') {
4093 this.titleString = this.titleString.substr(1);
4094 if (this.titleString != '') {
4095 this.titleString += ' - ';
4097 this.titleString += 'Shell In A Box'
4100 window.document.title = this.titleString;
4103 this.isEsc = ESnormal;
4105 this.titleString += String.fromCharCode(ch);
4111 ch = this.GMap[this.isEsc - ESss2 + 2]
4112 [this.toggleMeta ? (ch | 0x80) : ch];
4113 if ((ch & 0xFF00) == 0xF000) {
4115 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4116 this.isEsc = ESnormal; break;
4119 this.lastCharacter = String.fromCharCode(ch);
4120 lineBuf += this.lastCharacter;
4121 this.isEsc = ESnormal; break;
4123 this.isEsc = ESnormal; break;
4130 VT100.prototype.renderString = function(s, showCursor) {
4131 if (this.printing) {
4132 this.sendToPrinter(s);
4139 // We try to minimize the number of DOM operations by coalescing individual
4140 // characters into strings. This is a significant performance improvement.
4141 var incX = s.length;
4142 if (incX > this.terminalWidth - this.cursorX) {
4143 incX = this.terminalWidth - this.cursorX;
4147 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4150 // Minimize the number of calls to putString(), by avoiding a direct
4151 // call to this.showCursor()
4152 this.cursor.style.visibility = '';
4154 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4157 VT100.prototype.vt100 = function(s) {
4158 this.cursorNeedsShowing = this.hideCursor();
4159 this.respondString = '';
4161 for (var i = 0; i < s.length; i++) {
4162 var ch = s.charCodeAt(i);
4163 if (this.utfEnabled) {
4164 // Decode UTF8 encoded character
4166 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4167 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4168 if (--this.utfCount <= 0) {
4169 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4178 if ((ch & 0xE0) == 0xC0) {
4180 this.utfChar = ch & 0x1F;
4181 } else if ((ch & 0xF0) == 0xE0) {
4183 this.utfChar = ch & 0x0F;
4184 } else if ((ch & 0xF8) == 0xF0) {
4186 this.utfChar = ch & 0x07;
4187 } else if ((ch & 0xFC) == 0xF8) {
4189 this.utfChar = ch & 0x03;
4190 } else if ((ch & 0xFE) == 0xFC) {
4192 this.utfChar = ch & 0x01;
4202 var isNormalCharacter =
4203 (ch >= 32 && ch <= 127 || ch >= 160 ||
4204 this.utfEnabled && ch >= 128 ||
4205 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4206 (ch != 0x7F || this.dispCtrl);
4208 if (isNormalCharacter && this.isEsc == ESnormal) {
4210 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4212 if ((ch & 0xFF00) == 0xF000) {
4214 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4217 if (!this.printing) {
4218 if (this.needWrap || this.insertMode) {
4220 this.renderString(lineBuf);
4224 if (this.needWrap) {
4225 this.cr(); this.lf();
4227 if (this.insertMode) {
4228 this.scrollRegion(this.cursorX, this.cursorY,
4229 this.terminalWidth - this.cursorX - 1, 1,
4230 1, 0, this.color, this.style);
4233 this.lastCharacter = String.fromCharCode(ch);
4234 lineBuf += this.lastCharacter;
4235 if (!this.printing &&
4236 this.cursorX + lineBuf.length >= this.terminalWidth) {
4237 this.needWrap = this.autoWrapMode;
4241 this.renderString(lineBuf);
4244 var expand = this.doControl(ch);
4245 if (expand.length) {
4246 var r = this.respondString;
4247 this.respondString= r + this.vt100(expand);
4252 this.renderString(lineBuf, this.cursorNeedsShowing);
4253 } else if (this.cursorNeedsShowing) {
4256 return this.respondString;
4259 VT100.prototype.Latin1Map = [
4260 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4261 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4262 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4263 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4264 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4265 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4266 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4267 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4268 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4269 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4270 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4271 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4272 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4273 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4274 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4275 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4276 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4277 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4278 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4279 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4280 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4281 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4282 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4283 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4284 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4285 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4286 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4287 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4288 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4289 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4290 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4291 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4294 VT100.prototype.VT100GraphicsMap = [
4295 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4296 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4297 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4298 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4299 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4300 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4301 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4302 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4303 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4304 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4305 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4306 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4307 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4308 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4309 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4310 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4311 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4312 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4313 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4314 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4315 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4316 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4317 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4318 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4319 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4320 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4321 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4322 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4323 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4324 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4325 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4326 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4329 VT100.prototype.CodePage437Map = [
4330 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4331 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4332 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4333 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4334 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4335 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4336 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4337 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4338 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4339 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4340 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4341 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4342 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4343 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4344 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4345 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4346 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4347 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4348 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4349 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4350 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4351 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4352 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4353 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4354 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4355 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4356 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4357 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4358 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4359 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4360 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4361 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4364 VT100.prototype.DirectToFontMap = [
4365 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4366 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4367 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4368 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4369 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4370 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4371 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4372 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4373 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4374 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4375 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4376 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4377 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4378 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4379 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4380 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4381 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4382 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4383 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4384 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4385 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4386 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4387 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4388 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4389 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4390 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4391 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4392 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4393 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4394 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4395 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4396 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4399 VT100.prototype.ctrlAction = [
4400 true, false, false, false, false, false, false, true,
4401 true, true, true, true, true, true, true, true,
4402 false, false, false, false, false, false, false, false,
4403 true, false, true, true, false, false, false, false
4406 VT100.prototype.ctrlAlways = [
4407 true, false, false, false, false, false, false, false,
4408 true, false, true, false, true, true, true, true,
4409 false, false, false, false, false, false, false, false,
4410 false, false, false, true, false, false, false, false