1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
71 #define ESdeviceattr 5
87 #define ATTR_DEFAULT 0x00F0
88 #define ATTR_REVERSE 0x0100
89 #define ATTR_UNDERLINE 0x0200
90 #define ATTR_DIM 0x0400
91 #define ATTR_BRIGHT 0x0800
92 #define ATTR_BLINK 0x1000
98 function VT100(container) {
99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
106 // Optionally allow username and passwords.
107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
110 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
115 '(?::[1-9][0-9]*)?' +
118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
125 // Hostnames must end with a well-known top-level domain or must be
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
146 '(?::[1-9][0-9]{0,4})?' +
149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
155 '[-_.+a-zA-Z0-9]+@' +
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
173 // Optional arguments
174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
176 this.getUserSettings();
177 this.initializeElements(container);
178 this.maxScrollbackLines = 500;
181 this.isQuestionMark = false;
184 this.savedAttr = [ ];
185 this.savedUseGMap = 0;
186 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187 this.CodePage437Map, this.DirectToFontMap ];
188 this.savedValid = [ ];
189 this.respondString = '';
190 this.statusString = '';
191 this.internalClipboard = undefined;
195 VT100.prototype.reset = function(clearHistory) {
196 this.isEsc = ESnormal;
197 this.needWrap = false;
198 this.autoWrapMode = true;
199 this.dispCtrl = false;
200 this.toggleMeta = false;
201 this.insertMode = false;
202 this.applKeyMode = false;
203 this.cursorKeyMode = false;
204 this.crLfMode = false;
205 this.offsetMode = false;
206 this.mouseReporting = false;
207 this.printing = false;
208 if (typeof this.printWin != 'undefined' &&
209 this.printWin && !this.printWin.closed) {
210 this.printWin.close();
212 this.printWin = null;
213 this.utfEnabled = this.utfPreferred;
216 this.color = 'ansi0 bgAnsi15';
218 this.attr = ATTR_DEFAULT;
220 this.GMap = [ this.Latin1Map,
221 this.VT100GraphicsMap,
223 this.DirectToFontMap];
224 this.translate = this.GMap[this.useGMap];
226 this.bottom = this.terminalHeight;
227 this.lastCharacter = ' ';
228 this.userTabStop = [ ];
231 for (var i = 0; i < 2; i++) {
232 while (this.console[i].firstChild) {
233 this.console[i].removeChild(this.console[i].firstChild);
238 this.enableAlternateScreen(false);
240 var wasCompressed = false;
241 var transform = this.getTransformName();
243 for (var i = 0; i < 2; ++i) {
244 wasCompressed |= this.console[i].style[transform] != '';
245 this.console[i].style[transform] = '';
247 this.cursor.style[transform] = '';
248 this.space.style[transform] = '';
249 if (transform == 'filter') {
250 this.console[this.currentScreen].style.width = '';
260 this.isInverted = false;
261 this.refreshInvertedState();
262 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
263 this.color, this.style);
266 VT100.prototype.addListener = function(elem, event, listener) {
268 if (elem.addEventListener) {
269 elem.addEventListener(event, listener, false);
271 elem.attachEvent('on' + event, listener);
277 VT100.prototype.getUserSettings = function() {
278 // Compute hash signature to identify the entries in the userCSS menu.
279 // If the menu is unchanged from last time, default values can be
280 // looked up in a cookie associated with this page.
282 this.utfPreferred = true;
283 this.visualBell = typeof suppressAllAudio != 'undefined' &&
285 this.autoprint = true;
286 this.softKeyboard = false;
287 this.blinkingCursor = true;
288 if (this.visualBell) {
289 this.signature = Math.floor(16807*this.signature + 1) %
292 if (typeof userCSSList != 'undefined') {
293 for (var i = 0; i < userCSSList.length; ++i) {
294 var label = userCSSList[i][0];
295 for (var j = 0; j < label.length; ++j) {
296 this.signature = Math.floor(16807*this.signature+
297 label.charCodeAt(j)) %
300 if (userCSSList[i][1]) {
301 this.signature = Math.floor(16807*this.signature + 1) %
307 var key = 'shellInABox=' + this.signature + ':';
308 var settings = document.cookie.indexOf(key);
310 settings = document.cookie.substr(settings + key.length).
311 replace(/([0-1]*).*/, "$1");
312 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
313 0 : userCSSList.length)) {
314 this.utfPreferred = settings.charAt(0) != '0';
315 this.visualBell = settings.charAt(1) != '0';
316 this.autoprint = settings.charAt(2) != '0';
317 this.softKeyboard = settings.charAt(3) != '0';
318 this.blinkingCursor = settings.charAt(4) != '0';
319 if (typeof userCSSList != 'undefined') {
320 for (var i = 0; i < userCSSList.length; ++i) {
321 userCSSList[i][2] = settings.charAt(i + 5) != '0';
326 this.utfEnabled = this.utfPreferred;
329 VT100.prototype.storeUserSettings = function() {
330 var settings = 'shellInABox=' + this.signature + ':' +
331 (this.utfEnabled ? '1' : '0') +
332 (this.visualBell ? '1' : '0') +
333 (this.autoprint ? '1' : '0') +
334 (this.softKeyboard ? '1' : '0') +
335 (this.blinkingCursor ? '1' : '0');
336 if (typeof userCSSList != 'undefined') {
337 for (var i = 0; i < userCSSList.length; ++i) {
338 settings += userCSSList[i][2] ? '1' : '0';
342 d.setDate(d.getDate() + 3653);
343 document.cookie = settings + ';expires=' + d.toGMTString();
346 VT100.prototype.initializeUserCSSStyles = function() {
347 this.usercssActions = [];
348 if (typeof userCSSList != 'undefined') {
351 var wasSingleSel = 1;
352 var beginOfGroup = 0;
353 for (var i = 0; i <= userCSSList.length; ++i) {
354 if (i < userCSSList.length) {
355 var label = userCSSList[i][0];
356 var newGroup = userCSSList[i][1];
357 var enabled = userCSSList[i][2];
359 // Add user style sheet to document
360 var style = document.createElement('link');
361 var id = document.createAttribute('id');
362 id.nodeValue = 'usercss-' + i;
363 style.setAttributeNode(id);
364 var rel = document.createAttribute('rel');
365 rel.nodeValue = 'stylesheet';
366 style.setAttributeNode(rel);
367 var href = document.createAttribute('href');
368 href.nodeValue = 'usercss-' + i + '.css';
369 style.setAttributeNode(href);
370 var type = document.createAttribute('type');
371 type.nodeValue = 'text/css';
372 style.setAttributeNode(type);
373 document.getElementsByTagName('head')[0].appendChild(style);
374 style.disabled = !enabled;
378 if (newGroup || i == userCSSList.length) {
379 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
380 // The last group had multiple entries that are mutually exclusive;
381 // or the previous to last group did. In either case, we need to
382 // append a "<hr />" before we can add the last group to the menu.
385 wasSingleSel = i - beginOfGroup < 1;
389 for (var j = beginOfGroup; j < i; ++j) {
390 this.usercssActions[this.usercssActions.length] =
391 function(vt100, current, begin, count) {
393 // Deselect all other entries in the group, then either select
394 // (for multiple entries in group) or toggle (for on/off entry)
395 // the current entry.
397 var entry = vt100.getChildById(vt100.menu,
401 for (var c = count; c > 0; ++j) {
402 if (entry.tagName == 'LI') {
405 var label = vt100.usercss.childNodes[j];
407 // Restore label to just the text content
408 if (typeof label.textContent == 'undefined') {
409 var s = label.innerText;
410 label.innerHTML = '';
411 label.appendChild(document.createTextNode(s));
413 label.textContent= label.textContent;
416 // User style sheets are numbered sequentially
417 var sheet = document.getElementById(
421 sheet.disabled = !sheet.disabled;
423 sheet.disabled = false;
425 if (!sheet.disabled) {
426 label.innerHTML= '<img src="enabled.gif" />' +
430 sheet.disabled = true;
432 userCSSList[i][2] = !sheet.disabled;
435 entry = entry.nextSibling;
438 // If the font size changed, adjust cursor and line dimensions
439 this.cursor.style.cssText= '';
440 this.cursorWidth = this.cursor.clientWidth;
441 this.cursorHeight = this.lineheight.clientHeight;
442 for (i = 0; i < this.console.length; ++i) {
443 for (var line = this.console[i].firstChild; line;
444 line = line.nextSibling) {
445 line.style.height = this.cursorHeight + 'px';
450 }(this, j, beginOfGroup, i - beginOfGroup);
453 if (i == userCSSList.length) {
459 // Collect all entries in a group, before attaching them to the menu.
460 // This is necessary as we don't know whether this is a group of
461 // mutually exclusive options (which should be separated by "<hr />" on
462 // both ends), or whether this is a on/off toggle, which can be grouped
463 // together with other on/off options.
465 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
469 this.usercss.innerHTML = menu;
473 VT100.prototype.resetLastSelectedKey = function(e) {
474 var key = this.lastSelectedKey;
479 var position = this.mousePosition(e);
481 // We don't get all the necessary events to reliably reselect a key
482 // if we moved away from it and then back onto it. We approximate the
483 // behavior by remembering the key until either we release the mouse
484 // button (we might never get this event if the mouse has since left
485 // the window), or until we move away too far.
486 var box = this.keyboard.firstChild;
487 if (position[0] < box.offsetLeft + key.offsetWidth ||
488 position[1] < box.offsetTop + key.offsetHeight ||
489 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
490 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
491 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
492 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
493 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
494 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
495 if (this.lastSelectedKey.className) log.console('reset: deselecting');
496 this.lastSelectedKey.className = '';
497 this.lastSelectedKey = undefined;
502 VT100.prototype.showShiftState = function(state) {
503 var style = document.getElementById('shift_state');
505 this.setTextContentRaw(style,
506 '#vt100 #keyboard .shifted {' +
507 'display: inline }' +
508 '#vt100 #keyboard .unshifted {' +
511 this.setTextContentRaw(style, '');
513 var elems = this.keyboard.getElementsByTagName('I');
514 for (var i = 0; i < elems.length; ++i) {
515 if (elems[i].id == '16') {
516 elems[i].className = state ? 'selected' : '';
521 VT100.prototype.showCtrlState = function(state) {
522 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
524 ctrl.className = state ? 'selected' : '';
528 VT100.prototype.showAltState = function(state) {
529 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
531 alt.className = state ? 'selected' : '';
535 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
540 fake.shiftKey = shift;
543 return this.handleKey(fake);
546 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
547 if (elem == undefined) {
550 if (ch == '\u00A0') {
551 // should be treated as a regular space character.
554 if (ch != undefined && CH == undefined) {
555 // For letter keys, we automatically compute the uppercase character code
556 // from the lowercase one.
557 CH = ch.toUpperCase();
559 if (KEY == undefined && key != undefined) {
560 // Most keys have identically key codes for both lowercase and uppercase
561 // keypresses. Normally, only function keys would have distinct key codes,
562 // whereas regular keys have character codes.
564 } else if (KEY == undefined && CH != undefined) {
565 // For regular keys, copy the character code to the key code.
566 KEY = CH.charCodeAt(0);
568 if (key == undefined && ch != undefined) {
569 // For regular keys, copy the character code to the key code.
570 key = ch.charCodeAt(0);
572 // Convert characters to numeric character codes. If the character code
573 // is undefined (i.e. this is a function key), set it to zero.
574 ch = ch ? ch.charCodeAt(0) : 0;
575 CH = CH ? CH.charCodeAt(0) : 0;
577 // Mouse down events high light the key. We also set lastSelectedKey. This
578 // is needed to that mouseout/mouseover can keep track of the key that
579 // is currently being clicked.
580 this.addListener(elem, 'mousedown',
581 function(vt100, elem, key) { return function(e) {
582 if ((e.which || e.button) == 1) {
583 if (vt100.lastSelectedKey) {
584 vt100.lastSelectedKey.className= '';
586 // Highlight the key while the mouse button is held down.
587 if (key == 16 /* Shift */) {
588 if (!elem.className != vt100.isShift) {
589 vt100.showShiftState(!vt100.isShift);
591 } else if (key == 17 /* Ctrl */) {
592 if (!elem.className != vt100.isCtrl) {
593 vt100.showCtrlState(!vt100.isCtrl);
595 } else if (key == 18 /* Alt */) {
596 if (!elem.className != vt100.isAlt) {
597 vt100.showAltState(!vt100.isAlt);
600 elem.className = 'selected';
602 vt100.lastSelectedKey = elem;
604 return false; }; }(this, elem, key));
606 // Modifier keys update the state of the keyboard, but do not generate
607 // any key clicks that get forwarded to the application.
608 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
609 function(vt100, elem) { return function(e) {
610 if (elem == vt100.lastSelectedKey) {
611 if (key == 16 /* Shift */) {
612 // The user clicked the Shift key
613 vt100.isShift = !vt100.isShift;
614 vt100.showShiftState(vt100.isShift);
615 } else if (key == 17 /* Ctrl */) {
616 vt100.isCtrl = !vt100.isCtrl;
617 vt100.showCtrlState(vt100.isCtrl);
618 } else if (key == 18 /* Alt */) {
619 vt100.isAlt = !vt100.isAlt;
620 vt100.showAltState(vt100.isAlt);
622 vt100.lastSelectedKey = undefined;
624 if (vt100.lastSelectedKey) {
625 vt100.lastSelectedKey.className = '';
626 vt100.lastSelectedKey = undefined;
628 return false; }; }(this, elem) :
629 // Regular keys generate key clicks, when the mouse button is released or
630 // when a mouse click event is received.
631 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
632 if (vt100.lastSelectedKey) {
633 if (elem == vt100.lastSelectedKey) {
634 // The user clicked a key.
636 vt100.clickedKeyboard(e, elem, CH, KEY,
637 true, vt100.isCtrl, vt100.isAlt);
639 vt100.clickedKeyboard(e, elem, ch, key,
640 false, vt100.isCtrl, vt100.isAlt);
642 vt100.isShift = false;
643 vt100.showShiftState(false);
644 vt100.isCtrl = false;
645 vt100.showCtrlState(false);
647 vt100.showAltState(false);
649 vt100.lastSelectedKey.className = '';
650 vt100.lastSelectedKey = undefined;
653 return false; }; }(this, elem, ch, key, CH, KEY);
654 this.addListener(elem, 'mouseup', clicked);
655 this.addListener(elem, 'click', clicked);
657 // When moving the mouse away from a key, check if any keys need to be
659 this.addListener(elem, 'mouseout',
660 function(vt100, elem, key) { return function(e) {
661 if (key == 16 /* Shift */) {
662 if (!elem.className == vt100.isShift) {
663 vt100.showShiftState(vt100.isShift);
665 } else if (key == 17 /* Ctrl */) {
666 if (!elem.className == vt100.isCtrl) {
667 vt100.showCtrlState(vt100.isCtrl);
669 } else if (key == 18 /* Alt */) {
670 if (!elem.className == vt100.isAlt) {
671 vt100.showAltState(vt100.isAlt);
673 } else if (elem.className) {
675 vt100.lastSelectedKey = elem;
676 } else if (vt100.lastSelectedKey) {
677 vt100.resetLastSelectedKey(e);
679 return false; }; }(this, elem, key));
681 // When moving the mouse over a key, select it if the user is still holding
682 // the mouse button down (i.e. elem == lastSelectedKey)
683 this.addListener(elem, 'mouseover',
684 function(vt100, elem, key) { return function(e) {
685 if (elem == vt100.lastSelectedKey) {
686 if (key == 16 /* Shift */) {
687 if (!elem.className != vt100.isShift) {
688 vt100.showShiftState(!vt100.isShift);
690 } else if (key == 17 /* Ctrl */) {
691 if (!elem.className != vt100.isCtrl) {
692 vt100.showCtrlState(!vt100.isCtrl);
694 } else if (key == 18 /* Alt */) {
695 if (!elem.className != vt100.isAlt) {
696 vt100.showAltState(!vt100.isAlt);
698 } else if (!elem.className) {
699 elem.className = 'selected';
702 vt100.resetLastSelectedKey(e);
704 return false; }; }(this, elem, key));
707 VT100.prototype.initializeKeyBindings = function(elem) {
709 if (elem.nodeName == "I" || elem.nodeName == "B") {
711 // Function keys. The Javascript keycode is part of the "id"
712 var i = parseInt(elem.id);
714 // If the id does not parse as a number, it is not a keycode.
715 this.addKeyBinding(elem, undefined, i);
718 var child = elem.firstChild;
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;
2404 VT100.prototype.about = function() {
2405 alert("VT100 Terminal Emulator " + VERSION +
2406 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2407 "For more information check http://shellinabox.com");
2410 VT100.prototype.hideContextMenu = function() {
2411 this.menu.style.visibility = 'hidden';
2412 this.menu.style.top = '-100px';
2413 this.menu.style.left = '-100px';
2414 this.menu.style.width = '0px';
2415 this.menu.style.height = '0px';
2418 VT100.prototype.extendContextMenu = function(entries, actions) {
2421 VT100.prototype.showContextMenu = function(x, y) {
2422 this.menu.innerHTML =
2423 '<table class="popup" ' +
2424 'cellpadding="0" cellspacing="0">' +
2426 '<ul id="menuentries">' +
2427 '<li id="beginclipboard">Copy</li>' +
2428 '<li id="endclipboard">Paste</li>' +
2430 '<li id="reset">Reset</li>' +
2432 '<li id="beginconfig">' +
2433 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2436 (this.visualBell ? '<img src="enabled.gif" />' : '') +
2439 (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2440 'Onscreen Keyboard</li>' +
2441 '<li id="endconfig">' +
2442 (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2443 'Blinking Cursor</li>'+
2444 (this.usercss.firstChild ?
2445 '<hr id="beginusercss" />' +
2446 this.usercss.innerHTML +
2447 '<hr id="endusercss" />' :
2449 '<li id="about">About...</li>' +
2454 var popup = this.menu.firstChild;
2455 var menuentries = this.getChildById(popup, 'menuentries');
2457 // Determine menu entries that should be disabled
2458 this.lastSelection = this.selection();
2459 if (!this.lastSelection.length) {
2460 menuentries.firstChild.className
2463 var p = this.pasteFnc();
2465 menuentries.childNodes[1].className
2469 // Actions for default items
2470 var actions = [ this.copyLast, p, this.reset,
2471 this.toggleUTF, this.toggleBell,
2472 this.toggleSoftKeyboard,
2473 this.toggleCursorBlinking ];
2475 // Actions for user CSS styles (if any)
2476 for (var i = 0; i < this.usercssActions.length; ++i) {
2477 actions[actions.length] = this.usercssActions[i];
2479 actions[actions.length] = this.about;
2481 // Allow subclasses to dynamically add entries to the context menu
2482 this.extendContextMenu(menuentries, actions);
2484 // Hook up event listeners
2485 for (var node = menuentries.firstChild, i = 0; node;
2486 node = node.nextSibling) {
2487 if (node.tagName == 'LI') {
2488 if (node.className != 'disabled') {
2489 this.addListener(node, 'mouseover',
2490 function(vt100, node) {
2492 node.className = 'hover';
2495 this.addListener(node, 'mouseout',
2496 function(vt100, node) {
2498 node.className = '';
2501 this.addListener(node, 'mousedown',
2502 function(vt100, action) {
2503 return function(event) {
2504 vt100.hideContextMenu();
2506 vt100.storeUserSettings();
2507 return vt100.cancelEvent(event || window.event);
2509 }(this, actions[i]));
2510 this.addListener(node, 'mouseup',
2512 return function(event) {
2513 return vt100.cancelEvent(event || window.event);
2516 this.addListener(node, 'mouseclick',
2518 return function(event) {
2519 return vt100.cancelEvent(event || window.event);
2527 // Position menu next to the mouse pointer
2528 this.menu.style.left = '0px';
2529 this.menu.style.top = '0px';
2530 this.menu.style.width = this.container.offsetWidth + 'px';
2531 this.menu.style.height = this.container.offsetHeight + 'px';
2532 popup.style.left = '0px';
2533 popup.style.top = '0px';
2536 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2537 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2542 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2543 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2548 popup.style.left = x + 'px';
2549 popup.style.top = y + 'px';
2551 // Block all other interactions with the terminal emulator
2552 this.addListener(this.menu, 'click', function(vt100) {
2554 vt100.hideContextMenu();
2559 this.menu.style.visibility = '';
2562 VT100.prototype.keysPressed = function(ch) {
2563 for (var i = 0; i < ch.length; i++) {
2564 var c = ch.charCodeAt(i);
2565 this.vt100(c >= 7 && c <= 15 ||
2566 c == 24 || c == 26 || c == 27 || c >= 32
2567 ? String.fromCharCode(c) : '<' + c + '>');
2571 VT100.prototype.applyModifiers = function(ch, event) {
2573 if (event.ctrlKey) {
2574 if (ch >= 32 && ch <= 127) {
2575 // For historic reasons, some control characters are treated specially
2577 case /* 3 */ 51: ch = 27; break;
2578 case /* 4 */ 52: ch = 28; break;
2579 case /* 5 */ 53: ch = 29; break;
2580 case /* 6 */ 54: ch = 30; break;
2581 case /* 7 */ 55: ch = 31; break;
2582 case /* 8 */ 56: ch = 127; break;
2583 case /* ? */ 63: ch = 127; break;
2584 default: ch &= 31; break;
2588 return String.fromCharCode(ch);
2594 VT100.prototype.handleKey = function(event) {
2595 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2596 // (event.shiftKey || event.ctrlKey || event.altKey ||
2597 // event.metaKey ? ', ' +
2598 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2599 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2602 if (typeof event.charCode != 'undefined') {
2603 // non-IE keypress events have a translated charCode value. Also, our
2604 // fake events generated when receiving keydown events include this data
2606 ch = event.charCode;
2607 key = event.keyCode;
2609 // When sending a keypress event, IE includes the translated character
2610 // code in the keyCode field.
2615 // Apply modifier keys (ctrl and shift)
2619 ch = this.applyModifiers(ch, event);
2621 // By this point, "ch" is either defined and contains the character code, or
2622 // it is undefined and "key" defines the code of a function key
2623 if (ch != undefined) {
2624 this.scrollable.scrollTop = this.numScrollbackLines *
2625 this.cursorHeight + 1;
2627 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2628 // Many programs have difficulties dealing with parametrized escape
2629 // sequences for function keys. Thus, if ALT is the only modifier
2630 // key, return Emacs-style keycodes for commonly used keys.
2632 case 33: /* Page Up */ ch = '\u001B<'; break;
2633 case 34: /* Page Down */ ch = '\u001B>'; break;
2634 case 37: /* Left */ ch = '\u001Bb'; break;
2635 case 38: /* Up */ ch = '\u001Bp'; break;
2636 case 39: /* Right */ ch = '\u001Bf'; break;
2637 case 40: /* Down */ ch = '\u001Bn'; break;
2638 case 46: /* Delete */ ch = '\u001Bd'; break;
2641 } else if (event.shiftKey && !event.ctrlKey &&
2642 !event.altKey && !event.metaKey) {
2644 case 33: /* Page Up */ this.scrollBack(); return;
2645 case 34: /* Page Down */ this.scrollFore(); return;
2649 if (ch == undefined) {
2651 case 8: /* Backspace */ ch = '\u007f'; break;
2652 case 9: /* Tab */ ch = '\u0009'; break;
2653 case 10: /* Return */ ch = '\u000A'; break;
2654 case 13: /* Enter */ ch = this.crLfMode ?
2655 '\r\n' : '\r'; break;
2656 case 16: /* Shift */ return;
2657 case 17: /* Ctrl */ return;
2658 case 18: /* Alt */ return;
2659 case 19: /* Break */ return;
2660 case 20: /* Caps Lock */ return;
2661 case 27: /* Escape */ ch = '\u001B'; break;
2662 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2663 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2664 case 35: /* End */ ch = '\u001BOF'; break;
2665 case 36: /* Home */ ch = '\u001BOH'; break;
2666 case 37: /* Left */ ch = this.cursorKeyMode ?
2667 '\u001BOD' : '\u001B[D'; break;
2668 case 38: /* Up */ ch = this.cursorKeyMode ?
2669 '\u001BOA' : '\u001B[A'; break;
2670 case 39: /* Right */ ch = this.cursorKeyMode ?
2671 '\u001BOC' : '\u001B[C'; break;
2672 case 40: /* Down */ ch = this.cursorKeyMode ?
2673 '\u001BOB' : '\u001B[B'; break;
2674 case 45: /* Insert */ ch = '\u001B[2~'; break;
2675 case 46: /* Delete */ ch = '\u001B[3~'; break;
2676 case 91: /* Left Window */ return;
2677 case 92: /* Right Window */ return;
2678 case 93: /* Select */ return;
2679 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2680 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2681 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2682 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2683 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2684 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2685 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2686 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2687 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2688 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2689 case 106: /* * */ ch = this.applyModifiers(42, event); break;
2690 case 107: /* + */ ch = this.applyModifiers(43, event); break;
2691 case 109: /* - */ ch = this.applyModifiers(45, event); break;
2692 case 110: /* . */ ch = this.applyModifiers(46, event); break;
2693 case 111: /* / */ ch = this.applyModifiers(47, event); break;
2694 case 112: /* F1 */ ch = '\u001BOP'; break;
2695 case 113: /* F2 */ ch = '\u001BOQ'; break;
2696 case 114: /* F3 */ ch = '\u001BOR'; break;
2697 case 115: /* F4 */ ch = '\u001BOS'; break;
2698 case 116: /* F5 */ ch = '\u001B[15~'; break;
2699 case 117: /* F6 */ ch = '\u001B[17~'; break;
2700 case 118: /* F7 */ ch = '\u001B[18~'; break;
2701 case 119: /* F8 */ ch = '\u001B[19~'; break;
2702 case 120: /* F9 */ ch = '\u001B[20~'; break;
2703 case 121: /* F10 */ ch = '\u001B[21~'; break;
2704 case 122: /* F11 */ ch = '\u001B[23~'; break;
2705 case 123: /* F12 */ ch = '\u001B[24~'; break;
2706 case 144: /* Num Lock */ return;
2707 case 145: /* Scroll Lock */ return;
2708 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2709 case 187: /* = */ ch = this.applyModifiers(61, event); break;
2710 case 188: /* , */ ch = this.applyModifiers(44, event); break;
2711 case 189: /* - */ ch = this.applyModifiers(45, event); break;
2712 case 190: /* . */ ch = this.applyModifiers(46, event); break;
2713 case 191: /* / */ ch = this.applyModifiers(47, event); break;
2714 case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2715 case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2716 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2717 case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2718 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2721 this.scrollable.scrollTop = this.numScrollbackLines *
2722 this.cursorHeight + 1;
2726 // "ch" now contains the sequence of keycodes to send. But we might still
2727 // have to apply the effects of modifier keys.
2728 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2729 var start, digit, part1, part2;
2730 if ((start = ch.substr(0, 2)) == '\u001B[') {
2732 part1.length < ch.length &&
2733 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2734 part1 = ch.substr(0, part1.length + 1);
2736 part2 = ch.substr(part1.length);
2737 if (part1.length > 2) {
2740 } else if (start == '\u001BO') {
2742 part2 = ch.substr(2);
2744 if (part1 != undefined) {
2746 ((event.shiftKey ? 1 : 0) +
2747 (event.altKey|event.metaKey ? 2 : 0) +
2748 (event.ctrlKey ? 4 : 0)) +
2750 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2755 if (this.menu.style.visibility == 'hidden') {
2756 // this.vt100('R: c=');
2757 // for (var i = 0; i < ch.length; i++)
2758 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2759 // this.vt100('\r\n');
2760 this.keysPressed(ch);
2764 VT100.prototype.inspect = function(o, d) {
2765 if (d == undefined) {
2769 if (typeof o == 'object' && ++d < 2) {
2772 rc += this.spaces(d * 2) + i + ' -> ';
2774 rc += this.inspect(o[i], d);
2776 rc += '?' + '?' + '?\r\n';
2781 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2786 VT100.prototype.checkComposedKeys = function(event) {
2787 // Composed keys (at least on Linux) do not generate normal events.
2788 // Instead, they get entered into the text field. We normally catch
2789 // this on the next keyup event.
2790 var s = this.input.value;
2792 this.input.value = '';
2793 if (this.menu.style.visibility == 'hidden') {
2794 this.keysPressed(s);
2799 VT100.prototype.fixEvent = function(event) {
2800 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2801 // is used as a second-level selector, clear the modifier bits before
2802 // handling the event.
2803 if (event.ctrlKey && event.altKey) {
2805 fake.charCode = event.charCode;
2806 fake.keyCode = event.keyCode;
2807 fake.ctrlKey = false;
2808 fake.shiftKey = event.shiftKey;
2809 fake.altKey = false;
2810 fake.metaKey = event.metaKey;
2814 // Some browsers fail to translate keys, if both shift and alt/meta is
2815 // pressed at the same time. We try to translate those cases, but that
2816 // only works for US keyboard layouts.
2817 if (event.shiftKey) {
2820 switch (this.lastNormalKeyDownEvent.keyCode) {
2821 case 39: /* ' -> " */ u = 39; s = 34; break;
2822 case 44: /* , -> < */ u = 44; s = 60; break;
2823 case 45: /* - -> _ */ u = 45; s = 95; break;
2824 case 46: /* . -> > */ u = 46; s = 62; break;
2825 case 47: /* / -> ? */ u = 47; s = 63; break;
2827 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2828 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2829 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2830 case 51: /* 3 -> # */ u = 51; s = 35; break;
2831 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2832 case 53: /* 5 -> % */ u = 53; s = 37; break;
2833 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2834 case 55: /* 7 -> & */ u = 55; s = 38; break;
2835 case 56: /* 8 -> * */ u = 56; s = 42; break;
2836 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2838 case 59: /* ; -> : */ u = 59; s = 58; break;
2839 case 61: /* = -> + */ u = 61; s = 43; break;
2840 case 91: /* [ -> { */ u = 91; s = 123; break;
2841 case 92: /* \ -> | */ u = 92; s = 124; break;
2842 case 93: /* ] -> } */ u = 93; s = 125; break;
2843 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2845 case 109: /* - -> _ */ u = 45; s = 95; break;
2846 case 111: /* / -> ? */ u = 47; s = 63; break;
2848 case 186: /* ; -> : */ u = 59; s = 58; break;
2849 case 187: /* = -> + */ u = 61; s = 43; break;
2850 case 188: /* , -> < */ u = 44; s = 60; break;
2851 case 189: /* - -> _ */ u = 45; s = 95; break;
2852 case 190: /* . -> > */ u = 46; s = 62; break;
2853 case 191: /* / -> ? */ u = 47; s = 63; break;
2854 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2855 case 219: /* [ -> { */ u = 91; s = 123; break;
2856 case 220: /* \ -> | */ u = 92; s = 124; break;
2857 case 221: /* ] -> } */ u = 93; s = 125; break;
2858 case 222: /* ' -> " */ u = 39; s = 34; break;
2861 if (s && (event.charCode == u || event.charCode == 0)) {
2864 fake.keyCode = event.keyCode;
2865 fake.ctrlKey = event.ctrlKey;
2866 fake.shiftKey = event.shiftKey;
2867 fake.altKey = event.altKey;
2868 fake.metaKey = event.metaKey;
2875 VT100.prototype.keyDown = function(event) {
2876 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2877 // (event.shiftKey || event.ctrlKey || event.altKey ||
2878 // event.metaKey ? ', ' +
2879 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2880 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2882 this.checkComposedKeys(event);
2883 this.lastKeyPressedEvent = undefined;
2884 this.lastKeyDownEvent = undefined;
2885 this.lastNormalKeyDownEvent = event;
2888 event.keyCode == 32 ||
2889 event.keyCode >= 48 && event.keyCode <= 57 ||
2890 event.keyCode >= 65 && event.keyCode <= 90;
2893 event.keyCode >= 96 && event.keyCode <= 105 ||
2894 event.keyCode == 226;
2897 event.keyCode == 59 || event.keyCode == 61 ||
2898 event.keyCode == 106 || event.keyCode == 107 ||
2899 event.keyCode >= 109 && event.keyCode <= 111 ||
2900 event.keyCode >= 186 && event.keyCode <= 192 ||
2901 event.keyCode >= 219 && event.keyCode <= 223 ||
2902 event.keyCode == 252;
2904 if (navigator.appName == 'Konqueror') {
2905 normalKey |= event.keyCode < 128;
2910 // We normally prefer to look at keypress events, as they perform the
2911 // translation from keyCode to charCode. This is important, as the
2912 // translation is locale-dependent.
2913 // But for some keys, we must intercept them during the keydown event,
2914 // as they would otherwise get interpreted by the browser.
2915 // Even, when doing all of this, there are some keys that we can never
2916 // intercept. This applies to some of the menu navigation keys in IE.
2917 // In fact, we see them, but we cannot stop IE from seeing them, too.
2918 if ((event.charCode || event.keyCode) &&
2919 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2921 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2922 // interpret this sequence ourselves, as some keyboard layouts use
2923 // it for second-level layouts.
2924 !(event.ctrlKey && event.altKey)) ||
2925 this.catchModifiersEarly && normalKey && !alphNumKey &&
2926 (event.ctrlKey || event.altKey || event.metaKey) ||
2928 this.lastKeyDownEvent = event;
2930 fake.ctrlKey = event.ctrlKey;
2931 fake.shiftKey = event.shiftKey;
2932 fake.altKey = event.altKey;
2933 fake.metaKey = event.metaKey;
2935 fake.charCode = event.keyCode;
2939 fake.keyCode = event.keyCode;
2940 if (!alphNumKey && event.shiftKey) {
2941 fake = this.fixEvent(fake);
2945 this.handleKey(fake);
2946 this.lastNormalKeyDownEvent = undefined;
2949 // For non-IE browsers
2950 event.stopPropagation();
2951 event.preventDefault();
2956 event.cancelBubble = true;
2957 event.returnValue = false;
2967 VT100.prototype.keyPressed = function(event) {
2968 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2969 // (event.shiftKey || event.ctrlKey || event.altKey ||
2970 // event.metaKey ? ', ' +
2971 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2972 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2974 if (this.lastKeyDownEvent) {
2975 // If we already processed the key on keydown, do not process it
2976 // again here. Ideally, the browser should not even have generated a
2977 // keypress event in this case. But that does not appear to always work.
2978 this.lastKeyDownEvent = undefined;
2980 this.handleKey(event.altKey || event.metaKey
2981 ? this.fixEvent(event) : event);
2985 // For non-IE browsers
2986 event.preventDefault();
2992 event.cancelBubble = true;
2993 event.returnValue = false;
2998 this.lastNormalKeyDownEvent = undefined;
2999 this.lastKeyPressedEvent = event;
3003 VT100.prototype.keyUp = function(event) {
3004 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3005 // (event.shiftKey || event.ctrlKey || event.altKey ||
3006 // event.metaKey ? ', ' +
3007 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3008 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3010 if (this.lastKeyPressedEvent) {
3011 // The compose key on Linux occasionally confuses the browser and keeps
3012 // inserting bogus characters into the input field, even if just a regular
3013 // key has been pressed. Detect this case and drop the bogus characters.
3015 event.srcElement).value = '';
3017 // This is usually were we notice that a key has been composed and
3018 // thus failed to generate normal events.
3019 this.checkComposedKeys(event);
3021 // Some browsers don't report keypress events if ctrl or alt is pressed
3022 // for non-alphanumerical keys. Patch things up for now, but in the
3023 // future we will catch these keys earlier (in the keydown handler).
3024 if (this.lastNormalKeyDownEvent) {
3025 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3026 this.catchModifiersEarly = true;
3028 event.keyCode == 32 ||
3029 event.keyCode >= 48 && event.keyCode <= 57 ||
3030 event.keyCode >= 65 && event.keyCode <= 90;
3033 event.keyCode >= 96 && event.keyCode <= 105;
3036 event.keyCode == 59 || event.keyCode == 61 ||
3037 event.keyCode == 106 || event.keyCode == 107 ||
3038 event.keyCode >= 109 && event.keyCode <= 111 ||
3039 event.keyCode >= 186 && event.keyCode <= 192 ||
3040 event.keyCode >= 219 && event.keyCode <= 223 ||
3041 event.keyCode == 252;
3043 fake.ctrlKey = event.ctrlKey;
3044 fake.shiftKey = event.shiftKey;
3045 fake.altKey = event.altKey;
3046 fake.metaKey = event.metaKey;
3048 fake.charCode = event.keyCode;
3052 fake.keyCode = event.keyCode;
3053 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3054 fake = this.fixEvent(fake);
3057 this.lastNormalKeyDownEvent = undefined;
3058 this.handleKey(fake);
3064 event.cancelBubble = true;
3065 event.returnValue = false;
3070 this.lastKeyDownEvent = undefined;
3071 this.lastKeyPressedEvent = undefined;
3075 VT100.prototype.animateCursor = function(inactive) {
3076 if (!this.cursorInterval) {
3077 this.cursorInterval = setInterval(
3080 vt100.animateCursor();
3082 // Use this opportunity to check whether the user entered a composed
3083 // key, or whether somebody pasted text into the textfield.
3084 vt100.checkComposedKeys();
3088 if (inactive != undefined || this.cursor.className != 'inactive') {
3090 this.cursor.className = 'inactive';
3092 if (this.blinkingCursor) {
3093 this.cursor.className = this.cursor.className == 'bright'
3096 this.cursor.className = 'bright';
3102 VT100.prototype.blurCursor = function() {
3103 this.animateCursor(true);
3106 VT100.prototype.focusCursor = function() {
3107 this.animateCursor(false);
3110 VT100.prototype.flashScreen = function() {
3111 this.isInverted = !this.isInverted;
3112 this.refreshInvertedState();
3113 this.isInverted = !this.isInverted;
3114 setTimeout(function(vt100) {
3116 vt100.refreshInvertedState();
3121 VT100.prototype.beep = function() {
3122 if (this.visualBell) {
3129 this.beeper.src = 'beep.wav';
3136 VT100.prototype.bs = function() {
3137 if (this.cursorX > 0) {
3138 this.gotoXY(this.cursorX - 1, this.cursorY);
3139 this.needWrap = false;
3143 VT100.prototype.ht = function(count) {
3144 if (count == undefined) {
3147 var cx = this.cursorX;
3148 while (count-- > 0) {
3149 while (cx++ < this.terminalWidth) {
3150 var tabState = this.userTabStop[cx];
3151 if (tabState == false) {
3152 // Explicitly cleared tab stop
3154 } else if (tabState) {
3155 // Explicitly set tab stop
3158 // Default tab stop at each eighth column
3165 if (cx > this.terminalWidth - 1) {
3166 cx = this.terminalWidth - 1;
3168 if (cx != this.cursorX) {
3169 this.gotoXY(cx, this.cursorY);
3173 VT100.prototype.rt = function(count) {
3174 if (count == undefined) {
3177 var cx = this.cursorX;
3178 while (count-- > 0) {
3180 var tabState = this.userTabStop[cx];
3181 if (tabState == false) {
3182 // Explicitly cleared tab stop
3184 } else if (tabState) {
3185 // Explicitly set tab stop
3188 // Default tab stop at each eighth column
3198 if (cx != this.cursorX) {
3199 this.gotoXY(cx, this.cursorY);
3203 VT100.prototype.cr = function() {
3204 this.gotoXY(0, this.cursorY);
3205 this.needWrap = false;
3208 VT100.prototype.lf = function(count) {
3209 if (count == undefined) {
3212 if (count > this.terminalHeight) {
3213 count = this.terminalHeight;
3219 while (count-- > 0) {
3220 if (this.cursorY == this.bottom - 1) {
3221 this.scrollRegion(0, this.top + 1,
3222 this.terminalWidth, this.bottom - this.top - 1,
3223 0, -1, this.color, this.style);
3225 } else if (this.cursorY < this.terminalHeight - 1) {
3226 this.gotoXY(this.cursorX, this.cursorY + 1);
3231 VT100.prototype.ri = function(count) {
3232 if (count == undefined) {
3235 if (count > this.terminalHeight) {
3236 count = this.terminalHeight;
3242 while (count-- > 0) {
3243 if (this.cursorY == this.top) {
3244 this.scrollRegion(0, this.top,
3245 this.terminalWidth, this.bottom - this.top - 1,
3246 0, 1, this.color, this.style);
3247 } else if (this.cursorY > 0) {
3248 this.gotoXY(this.cursorX, this.cursorY - 1);
3251 this.needWrap = false;
3254 VT100.prototype.respondID = function() {
3255 this.respondString += '\u001B[?6c';
3258 VT100.prototype.respondSecondaryDA = function() {
3259 this.respondString += '\u001B[>0;0;0c';
3263 VT100.prototype.updateStyle = function() {
3265 if (this.attr & ATTR_UNDERLINE) {
3266 this.style = 'text-decoration:underline;';
3268 var bg = (this.attr >> 4) & 0xF;
3269 var fg = this.attr & 0xF;
3270 if (this.attr & ATTR_REVERSE) {
3275 if ((this.attr & (ATTR_REVERSE | ATTR_DIM)) == ATTR_DIM) {
3276 fg = 8; // Dark grey
3277 } else if (this.attr & ATTR_BRIGHT) {
3280 if (this.attr & ATTR_BLINK) {
3283 // Make some readability enhancements. Most notably, disallow identical
3284 // background and foreground colors.
3286 if ((fg ^= 8) == 7) {
3290 // And disallow bright colors on a light-grey background.
3291 if (bg == 7 && fg >= 8) {
3292 if ((fg -= 8) == 7) {
3297 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3300 VT100.prototype.setAttrColors = function(attr) {
3301 if (attr != this.attr) {
3307 VT100.prototype.saveCursor = function() {
3308 this.savedX[this.currentScreen] = this.cursorX;
3309 this.savedY[this.currentScreen] = this.cursorY;
3310 this.savedAttr[this.currentScreen] = this.attr;
3311 this.savedUseGMap = this.useGMap;
3312 for (var i = 0; i < 4; i++) {
3313 this.savedGMap[i] = this.GMap[i];
3315 this.savedValid[this.currentScreen] = true;
3318 VT100.prototype.restoreCursor = function() {
3319 if (!this.savedValid[this.currentScreen]) {
3322 this.attr = this.savedAttr[this.currentScreen];
3324 this.useGMap = this.savedUseGMap;
3325 for (var i = 0; i < 4; i++) {
3326 this.GMap[i] = this.savedGMap[i];
3328 this.translate = this.GMap[this.useGMap];
3329 this.needWrap = false;
3330 this.gotoXY(this.savedX[this.currentScreen],
3331 this.savedY[this.currentScreen]);
3334 VT100.prototype.getTransformName = function() {
3335 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3336 for (var i = 0; i < styles.length; ++i) {
3337 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3344 VT100.prototype.getTransformStyle = function(transform, scale) {
3345 return scale && scale != 1.0
3346 ? transform == 'filter'
3347 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3348 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3349 "sizingMethod='auto expand')"
3350 : 'translateX(-50%) ' +
3351 'scaleX(' + (1.0/scale) + ') ' +
3356 VT100.prototype.set80_132Mode = function(state) {
3357 var transform = this.getTransformName();
3359 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3363 this.getTransformStyle(transform, 1.65):'';
3364 this.console[this.currentScreen].style[transform] = style;
3365 this.cursor.style[transform] = style;
3366 this.space.style[transform] = style;
3367 this.scale = state ? 1.65 : 1.0;
3368 if (transform == 'filter') {
3369 this.console[this.currentScreen].style.width = state ? '165%' : '';
3375 VT100.prototype.setMode = function(state) {
3376 for (var i = 0; i <= this.npar; i++) {
3377 if (this.isQuestionMark) {
3378 switch (this.par[i]) {
3379 case 1: this.cursorKeyMode = state; break;
3380 case 3: this.set80_132Mode(state); break;
3381 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3382 case 6: this.offsetMode = state; break;
3383 case 7: this.autoWrapMode = state; break;
3385 case 9: this.mouseReporting = state; break;
3386 case 25: this.cursorNeedsShowing = state;
3387 if (state) { this.showCursor(); }
3388 else { this.hideCursor(); } break;
3391 case 47: this.enableAlternateScreen(state); break;
3395 switch (this.par[i]) {
3396 case 3: this.dispCtrl = state; break;
3397 case 4: this.insertMode = state; break;
3398 case 20:this.crLfMode = state; break;
3405 VT100.prototype.statusReport = function() {
3406 // Ready and operational.
3407 this.respondString += '\u001B[0n';
3410 VT100.prototype.cursorReport = function() {
3411 this.respondString += '\u001B[' +
3412 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3414 (this.cursorX + 1) +
3418 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3419 // Changing of cursor color is not implemented.
3422 VT100.prototype.openPrinterWindow = function() {
3425 if (!this.printWin || this.printWin.closed) {
3426 this.printWin = window.open('', 'print-output',
3427 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3428 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3429 this.printWin.document.body.innerHTML =
3430 '<link rel="stylesheet" href="' +
3431 document.location.protocol + '//' + document.location.host +
3432 document.location.pathname.replace(/[^/]*$/, '') +
3433 'print-styles.css" type="text/css">\n' +
3434 '<div id="options"><input id="autoprint" type="checkbox"' +
3435 (this.autoprint ? ' checked' : '') + '>' +
3436 'Automatically, print page(s) when job is ready' +
3437 '</input></div>\n' +
3438 '<div id="spacer"><input type="checkbox"> </input></div>' +
3439 '<pre id="print"></pre>\n';
3440 var autoprint = this.printWin.document.getElementById('autoprint');
3441 this.addListener(autoprint, 'click',
3442 (function(vt100, autoprint) {
3444 vt100.autoprint = autoprint.checked;
3445 vt100.storeUserSettings();
3448 })(this, autoprint));
3449 this.printWin.document.title = 'ShellInABox Printer Output';
3452 // Maybe, a popup blocker prevented us from working. Better catch the
3453 // exception, so that we won't break the entire terminal session. The
3454 // user probably needs to disable the blocker first before retrying the
3458 rc &= this.printWin && !this.printWin.closed &&
3459 (this.printWin.innerWidth ||
3460 this.printWin.document.documentElement.clientWidth ||
3461 this.printWin.document.body.clientWidth) > 1;
3463 if (!rc && this.printing == 100) {
3464 // Different popup blockers work differently. We try to detect a couple
3465 // of common methods. And then we retry again a brief amount later, as
3466 // false positives are otherwise possible. If we are sure that there is
3467 // a popup blocker in effect, we alert the user to it. This is helpful
3468 // as some popup blockers have minimal or no UI, and the user might not
3469 // notice that they are missing the popup. In any case, we only show at
3470 // most one message per print job.
3471 this.printing = true;
3472 setTimeout((function(win) {
3474 if (!win || win.closed ||
3476 win.document.documentElement.clientWidth ||
3477 win.document.body.clientWidth) <= 1) {
3478 alert('Attempted to print, but a popup blocker ' +
3479 'prevented the printer window from opening');
3482 })(this.printWin), 2000);
3487 VT100.prototype.sendToPrinter = function(s) {
3488 this.openPrinterWindow();
3490 var doc = this.printWin.document;
3491 var print = doc.getElementById('print');
3492 if (print.lastChild && print.lastChild.nodeName == '#text') {
3493 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3495 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3498 // There probably was a more aggressive popup blocker that prevented us
3499 // from accessing the printer windows.
3503 VT100.prototype.sendControlToPrinter = function(ch) {
3504 // We get called whenever doControl() is active. But for the printer, we
3505 // only implement a basic line printer that doesn't understand most of
3506 // the escape sequences of the VT100 terminal. In fact, the only escape
3507 // sequence that we really need to recognize is '^[[5i' for turning the
3513 this.openPrinterWindow();
3514 var doc = this.printWin.document;
3515 var print = doc.getElementById('print');
3516 var chars = print.lastChild &&
3517 print.lastChild.nodeName == '#text' ?
3518 print.lastChild.textContent.length : 0;
3519 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3526 this.openPrinterWindow();
3527 var pageBreak = this.printWin.document.createElement('div');
3528 pageBreak.className = 'pagebreak';
3529 pageBreak.innerHTML = '<hr />';
3530 this.printWin.document.getElementById('print').appendChild(pageBreak);
3534 this.openPrinterWindow();
3535 var lineBreak = this.printWin.document.createElement('br');
3536 this.printWin.document.getElementById('print').appendChild(lineBreak);
3543 switch (this.isEsc) {
3545 this.isEsc = ESnormal;
3548 this.isEsc = ESsquare;
3556 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3557 0, 0, 0, 0, 0, 0, 0, 0 ];
3558 this.isEsc = ESgetpars;
3559 this.isQuestionMark = ch == 0x3F /*?*/;
3560 if (this.isQuestionMark) {
3565 if (ch == 0x3B /*;*/) {
3568 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3569 var par = this.par[this.npar];
3570 if (par == undefined) {
3573 this.par[this.npar] = 10*par + (ch & 0xF);
3576 this.isEsc = ESgotpars;
3580 this.isEsc = ESnormal;
3581 if (this.isQuestionMark) {
3586 this.csii(this.par[0]);
3593 this.isEsc = ESnormal;
3599 // There probably was a more aggressive popup blocker that prevented us
3600 // from accessing the printer windows.
3604 VT100.prototype.csiAt = function(number) {
3609 if (number > this.terminalWidth - this.cursorX) {
3610 number = this.terminalWidth - this.cursorX;
3612 this.scrollRegion(this.cursorX, this.cursorY,
3613 this.terminalWidth - this.cursorX - number, 1,
3614 number, 0, this.color, this.style);
3615 this.needWrap = false;
3618 VT100.prototype.csii = function(number) {
3621 case 0: // Print Screen
3624 case 4: // Stop printing
3626 if (this.printing && this.printWin && !this.printWin.closed) {
3627 var print = this.printWin.document.getElementById('print');
3628 while (print.lastChild &&
3629 print.lastChild.tagName == 'DIV' &&
3630 print.lastChild.className == 'pagebreak') {
3631 // Remove trailing blank pages
3632 print.removeChild(print.lastChild);
3634 if (this.autoprint) {
3635 this.printWin.print();
3640 this.printing = false;
3642 case 5: // Start printing
3643 if (!this.printing && this.printWin && !this.printWin.closed) {
3644 this.printWin.document.getElementById('print').innerHTML = '';
3646 this.printing = 100;
3653 VT100.prototype.csiJ = function(number) {
3655 case 0: // Erase from cursor to end of display
3656 this.clearRegion(this.cursorX, this.cursorY,
3657 this.terminalWidth - this.cursorX, 1,
3658 this.color, this.style);
3659 if (this.cursorY < this.terminalHeight-2) {
3660 this.clearRegion(0, this.cursorY+1,
3661 this.terminalWidth, this.terminalHeight-this.cursorY-1,
3662 this.color, this.style);
3665 case 1: // Erase from start to cursor
3666 if (this.cursorY > 0) {
3667 this.clearRegion(0, 0,
3668 this.terminalWidth, this.cursorY,
3669 this.color, this.style);
3671 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3672 this.color, this.style);
3674 case 2: // Erase whole display
3675 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3676 this.color, this.style);
3684 VT100.prototype.csiK = function(number) {
3686 case 0: // Erase from cursor to end of line
3687 this.clearRegion(this.cursorX, this.cursorY,
3688 this.terminalWidth - this.cursorX, 1,
3689 this.color, this.style);
3691 case 1: // Erase from start of line to cursor
3692 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3693 this.color, this.style);
3695 case 2: // Erase whole line
3696 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3697 this.color, this.style);
3705 VT100.prototype.csiL = function(number) {
3706 // Open line by inserting blank line(s)
3707 if (this.cursorY >= this.bottom) {
3713 if (number > this.bottom - this.cursorY) {
3714 number = this.bottom - this.cursorY;
3716 this.scrollRegion(0, this.cursorY,
3717 this.terminalWidth, this.bottom - this.cursorY - number,
3718 0, number, this.color, this.style);
3722 VT100.prototype.csiM = function(number) {
3723 // Delete line(s), scrolling up the bottom of the screen.
3724 if (this.cursorY >= this.bottom) {
3730 if (number > this.bottom - this.cursorY) {
3731 number = bottom - cursorY;
3733 this.scrollRegion(0, this.cursorY + number,
3734 this.terminalWidth, this.bottom - this.cursorY - number,
3735 0, -number, this.color, this.style);
3739 VT100.prototype.csim = function() {
3740 for (var i = 0; i <= this.npar; i++) {
3741 switch (this.par[i]) {
3742 case 0: this.attr = ATTR_DEFAULT; break;
3743 case 1: this.attr = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT; break;
3744 case 2: this.attr = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM; break;
3745 case 4: this.attr |= ATTR_UNDERLINE; break;
3746 case 5: this.attr |= ATTR_BLINK; break;
3747 case 7: this.attr |= ATTR_REVERSE; break;
3749 this.translate = this.GMap[this.useGMap];
3750 this.dispCtrl = false;
3751 this.toggleMeta = false;
3754 this.translate = this.CodePage437Map;
3755 this.dispCtrl = true;
3756 this.toggleMeta = false;
3759 this.translate = this.CodePage437Map;
3760 this.dispCtrl = true;
3761 this.toggleMeta = true;
3764 case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM); break;
3765 case 24: this.attr &= ~ ATTR_UNDERLINE; break;
3766 case 25: this.attr &= ~ ATTR_BLINK; break;
3767 case 27: this.attr &= ~ ATTR_REVERSE; break;
3768 case 38: this.attr = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|
3769 ATTR_UNDERLINE; break;
3770 case 39: this.attr &= ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F); break;
3771 case 49: this.attr |= 0xF0; break;
3773 if (this.par[i] >= 30 && this.par[i] <= 37) {
3774 var fg = this.par[i] - 30;
3775 this.attr = (this.attr & ~0x0F) | fg;
3776 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3777 var bg = this.par[i] - 40;
3778 this.attr = (this.attr & ~0xF0) | (bg << 4);
3786 VT100.prototype.csiP = function(number) {
3787 // Delete character(s) following cursor
3791 if (number > this.terminalWidth - this.cursorX) {
3792 number = this.terminalWidth - this.cursorX;
3794 this.scrollRegion(this.cursorX + number, this.cursorY,
3795 this.terminalWidth - this.cursorX - number, 1,
3796 -number, 0, this.color, this.style);
3800 VT100.prototype.csiX = function(number) {
3801 // Clear characters following cursor
3805 if (number > this.terminalWidth - this.cursorX) {
3806 number = this.terminalWidth - this.cursorX;
3808 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3809 this.color, this.style);
3813 VT100.prototype.settermCommand = function() {
3814 // Setterm commands are not implemented
3817 VT100.prototype.doControl = function(ch) {
3818 if (this.printing) {
3819 this.sendControlToPrinter(ch);
3824 case 0x00: /* ignored */ break;
3825 case 0x08: this.bs(); break;
3826 case 0x09: this.ht(); break;
3830 case 0x84: this.lf(); if (!this.crLfMode) break;
3831 case 0x0D: this.cr(); break;
3832 case 0x85: this.cr(); this.lf(); break;
3833 case 0x0E: this.useGMap = 1;
3834 this.translate = this.GMap[1];
3835 this.dispCtrl = true; break;
3836 case 0x0F: this.useGMap = 0;
3837 this.translate = this.GMap[0];
3838 this.dispCtrl = false; break;
3840 case 0x1A: this.isEsc = ESnormal; break;
3841 case 0x1B: this.isEsc = ESesc; break;
3842 case 0x7F: /* ignored */ break;
3843 case 0x88: this.userTabStop[this.cursorX] = true; break;
3844 case 0x8D: this.ri(); break;
3845 case 0x8E: this.isEsc = ESss2; break;
3846 case 0x8F: this.isEsc = ESss3; break;
3847 case 0x9A: this.respondID(); break;
3848 case 0x9B: this.isEsc = ESsquare; break;
3849 case 0x07: if (this.isEsc != ESstatus) {
3853 default: switch (this.isEsc) {
3855 this.isEsc = ESnormal;
3857 /*%*/ case 0x25: this.isEsc = ESpercent; break;
3858 /*(*/ case 0x28: this.isEsc = ESsetG0; break;
3860 /*)*/ case 0x29: this.isEsc = ESsetG1; break;
3862 /***/ case 0x2A: this.isEsc = ESsetG2; break;
3864 /*+*/ case 0x2B: this.isEsc = ESsetG3; break;
3865 /*#*/ case 0x23: this.isEsc = EShash; break;
3866 /*7*/ case 0x37: this.saveCursor(); break;
3867 /*8*/ case 0x38: this.restoreCursor(); break;
3868 /*>*/ case 0x3E: this.applKeyMode = false; break;
3869 /*=*/ case 0x3D: this.applKeyMode = true; break;
3870 /*D*/ case 0x44: this.lf(); break;
3871 /*E*/ case 0x45: this.cr(); this.lf(); break;
3872 /*M*/ case 0x4D: this.ri(); break;
3873 /*N*/ case 0x4E: this.isEsc = ESss2; break;
3874 /*O*/ case 0x4F: this.isEsc = ESss3; break;
3875 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3876 /*Z*/ case 0x5A: this.respondID(); break;
3877 /*[*/ case 0x5B: this.isEsc = ESsquare; break;
3878 /*]*/ case 0x5D: this.isEsc = ESnonstd; break;
3879 /*c*/ case 0x63: this.reset(); break;
3880 /*g*/ case 0x67: this.flashScreen(); break;
3888 /*2*/ case 0x32: this.statusString = ''; this.isEsc = ESstatus; break;
3889 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3890 this.isEsc = ESpalette; break;
3891 /*R*/ case 0x52: // Palette support is not implemented
3892 this.isEsc = ESnormal; break;
3893 default: this.isEsc = ESnormal; break;
3897 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3898 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3899 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3900 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3902 if (this.npar == 7) {
3903 // Palette support is not implemented
3904 this.isEsc = ESnormal;
3907 this.isEsc = ESnormal;
3912 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3913 0, 0, 0, 0, 0, 0, 0, 0 ];
3914 this.isEsc = ESgetpars;
3915 /*[*/ if (ch == 0x5B) { // Function key
3916 this.isEsc = ESfunckey;
3919 /*?*/ this.isQuestionMark = ch == 0x3F;
3920 if (this.isQuestionMark) {
3927 /*;*/ if (ch == 0x3B) {
3930 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3931 var par = this.par[this.npar];
3932 if (par == undefined) {
3935 this.par[this.npar] = 10*par + (ch & 0xF);
3937 } else if (this.isEsc == ESdeviceattr) {
3939 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3940 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3941 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3942 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3945 this.isEsc = ESnormal;
3948 this.isEsc = ESgotpars;
3952 this.isEsc = ESnormal;
3953 if (this.isQuestionMark) {
3955 /*h*/ case 0x68: this.setMode(true); break;
3956 /*l*/ case 0x6C: this.setMode(false); break;
3957 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3960 this.isQuestionMark = false;
3964 /*!*/ case 0x21: this.isEsc = ESbang; break;
3965 /*>*/ case 0x3E: if (!this.npar) this.isEsc = ESdeviceattr; break;
3967 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3968 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3969 this.cursorY - (this.par[0] ? this.par[0] : 1));
3972 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3973 this.cursorY + (this.par[0] ? this.par[0] : 1));
3976 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3977 this.cursorY); break;
3978 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3979 this.cursorY); break;
3980 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3982 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3984 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3986 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3987 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3988 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3989 /*i*/ case 0x69: this.csii(this.par[0]); break;
3990 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3991 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3992 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3993 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3994 /*m*/ case 0x6D: this.csim(); break;
3995 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3996 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3997 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3998 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3999 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4000 /*g*/ case 0x67: if (this.par[0] == 0) {
4001 this.userTabStop[this.cursorX] = false;
4002 } else if (this.par[0] == 2 || this.par[0] == 3) {
4003 this.userTabStop = [ ];
4004 for (var i = 0; i < this.terminalWidth; i++) {
4005 this.userTabStop[i] = false;
4009 /*h*/ case 0x68: this.setMode(true); break;
4010 /*l*/ case 0x6C: this.setMode(false); break;
4011 /*n*/ case 0x6E: switch (this.par[0]) {
4012 case 5: this.statusReport(); break;
4013 case 6: this.cursorReport(); break;
4017 /*q*/ case 0x71: // LED control not implemented
4019 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4020 var b = this.par[1] ? this.par[1]
4021 : this.terminalHeight;
4022 if (t < b && b <= this.terminalHeight) {
4028 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4029 if (c > this.terminalWidth * this.terminalHeight) {
4030 c = this.terminalWidth * this.terminalHeight;
4033 lineBuf += this.lastCharacter;
4036 /*s*/ case 0x73: this.saveCursor(); break;
4037 /*u*/ case 0x75: this.restoreCursor(); break;
4038 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4039 /*]*/ case 0x5D: this.settermCommand(); break;
4047 this.isEsc = ESnormal;
4050 this.isEsc = ESnormal;
4052 /*@*/ case 0x40: this.utfEnabled = false; break;
4054 /*8*/ case 0x38: this.utfEnabled = true; break;
4059 this.isEsc = ESnormal; break;
4061 this.isEsc = ESnormal;
4062 /*8*/ if (ch == 0x38) {
4063 // Screen alignment test not implemented
4070 var g = this.isEsc - ESsetG0;
4071 this.isEsc = ESnormal;
4073 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4075 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4076 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4077 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4080 if (this.useGMap == g) {
4081 this.translate = this.GMap[g];
4086 if (this.statusString && this.statusString.charAt(0) == ';') {
4087 this.statusString = this.statusString.substr(1);
4090 window.status = this.statusString;
4093 this.isEsc = ESnormal;
4095 this.statusString += String.fromCharCode(ch);
4101 ch = this.GMap[this.isEsc - ESss2 + 2]
4102 [this.toggleMeta ? (ch | 0x80) : ch];
4103 if ((ch & 0xFF00) == 0xF000) {
4105 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4106 this.isEsc = ESnormal; break;
4109 this.lastCharacter = String.fromCharCode(ch);
4110 lineBuf += this.lastCharacter;
4111 this.isEsc = ESnormal; break;
4113 this.isEsc = ESnormal; break;
4120 VT100.prototype.renderString = function(s, showCursor) {
4121 if (this.printing) {
4122 this.sendToPrinter(s);
4129 // We try to minimize the number of DOM operations by coalescing individual
4130 // characters into strings. This is a significant performance improvement.
4131 var incX = s.length;
4132 if (incX > this.terminalWidth - this.cursorX) {
4133 incX = this.terminalWidth - this.cursorX;
4137 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4140 // Minimize the number of calls to putString(), by avoiding a direct
4141 // call to this.showCursor()
4142 this.cursor.style.visibility = '';
4144 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4147 VT100.prototype.vt100 = function(s) {
4148 this.cursorNeedsShowing = this.hideCursor();
4149 this.respondString = '';
4151 for (var i = 0; i < s.length; i++) {
4152 var ch = s.charCodeAt(i);
4153 if (this.utfEnabled) {
4154 // Decode UTF8 encoded character
4156 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4157 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4158 if (--this.utfCount <= 0) {
4159 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4168 if ((ch & 0xE0) == 0xC0) {
4170 this.utfChar = ch & 0x1F;
4171 } else if ((ch & 0xF0) == 0xE0) {
4173 this.utfChar = ch & 0x0F;
4174 } else if ((ch & 0xF8) == 0xF0) {
4176 this.utfChar = ch & 0x07;
4177 } else if ((ch & 0xFC) == 0xF8) {
4179 this.utfChar = ch & 0x03;
4180 } else if ((ch & 0xFE) == 0xFC) {
4182 this.utfChar = ch & 0x01;
4192 var isNormalCharacter =
4193 (ch >= 32 && ch <= 127 || ch >= 160 ||
4194 this.utfEnabled && ch >= 128 ||
4195 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4196 (ch != 0x7F || this.dispCtrl);
4198 if (isNormalCharacter && this.isEsc == ESnormal) {
4200 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4202 if ((ch & 0xFF00) == 0xF000) {
4204 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4207 if (!this.printing) {
4208 if (this.needWrap || this.insertMode) {
4210 this.renderString(lineBuf);
4214 if (this.needWrap) {
4215 this.cr(); this.lf();
4217 if (this.insertMode) {
4218 this.scrollRegion(this.cursorX, this.cursorY,
4219 this.terminalWidth - this.cursorX - 1, 1,
4220 1, 0, this.color, this.style);
4223 this.lastCharacter = String.fromCharCode(ch);
4224 lineBuf += this.lastCharacter;
4225 if (!this.printing &&
4226 this.cursorX + lineBuf.length >= this.terminalWidth) {
4227 this.needWrap = this.autoWrapMode;
4231 this.renderString(lineBuf);
4234 var expand = this.doControl(ch);
4235 if (expand.length) {
4236 var r = this.respondString;
4237 this.respondString= r + this.vt100(expand);
4242 this.renderString(lineBuf, this.cursorNeedsShowing);
4243 } else if (this.cursorNeedsShowing) {
4246 return this.respondString;
4249 VT100.prototype.Latin1Map = [
4250 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4251 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4252 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4253 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4254 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4255 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4256 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4257 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4258 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4259 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4260 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4261 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4262 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4263 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4264 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4265 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4266 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4267 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4268 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4269 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4270 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4271 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4272 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4273 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4274 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4275 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4276 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4277 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4278 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4279 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4280 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4281 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4284 VT100.prototype.VT100GraphicsMap = [
4285 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4286 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4287 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4288 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4289 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4290 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4291 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4292 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4293 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4294 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4295 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4296 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4297 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4298 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4299 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4300 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4301 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4302 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4303 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4304 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4305 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4306 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4307 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4308 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4309 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4310 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4311 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4312 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4313 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4314 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4315 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4316 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4319 VT100.prototype.CodePage437Map = [
4320 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4321 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4322 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4323 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4324 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4325 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4326 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4327 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4328 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4329 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4330 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4331 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4332 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4333 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4334 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4335 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4336 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4337 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4338 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4339 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4340 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4341 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4342 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4343 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4344 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4345 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4346 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4347 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4348 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4349 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4350 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4351 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4354 VT100.prototype.DirectToFontMap = [
4355 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4356 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4357 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4358 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4359 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4360 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4361 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4362 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4363 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4364 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4365 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4366 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4367 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4368 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4369 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4370 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4371 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4372 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4373 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4374 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4375 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4376 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4377 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4378 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4379 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4380 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4381 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4382 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4383 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4384 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4385 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4386 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4389 VT100.prototype.ctrlAction = [
4390 true, false, false, false, false, false, false, true,
4391 true, true, true, true, true, true, true, true,
4392 false, false, false, false, false, false, false, false,
4393 true, false, true, true, false, false, false, false
4396 VT100.prototype.ctrlAlways = [
4397 true, false, false, false, false, false, false, false,
4398 true, false, true, false, true, true, true, true,
4399 false, false, false, false, false, false, false, false,
4400 false, false, false, true, false, false, false, false