1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
69 // #define ESgetpars 3
70 // #define ESgotpars 4
71 // #define ESdeviceattr 5
72 // #define ESfunckey 6
79 // #define ESpercent 13
80 // #define ESignore 14
81 // #define ESnonstd 15
82 // #define ESpalette 16
83 // #define ESstatus 17
87 // #define ATTR_DEFAULT 0x00F0
88 // #define ATTR_REVERSE 0x0100
89 // #define ATTR_UNDERLINE 0x0200
90 // #define ATTR_DIM 0x0400
91 // #define ATTR_BRIGHT 0x0800
92 // #define ATTR_BLINK 0x1000
94 // #define MOUSE_DOWN 0
96 // #define MOUSE_CLICK 2
98 function VT100(container) {
99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
106 // Optionally allow username and passwords.
107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
110 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
115 '(?::[1-9][0-9]*)?' +
118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
125 // Hostnames must end with a well-known top-level domain or must be
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
146 '(?::[1-9][0-9]{0,4})?' +
149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
155 '[-_.+a-zA-Z0-9]+@' +
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
173 // Optional arguments
174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
176 this.initializeElements(container);
177 this.initializeAnsiColors();
178 this.maxScrollbackLines = 500;
181 this.isQuestionMark = false;
184 this.savedAttr = [ ];
185 this.savedUseGMap = 0;
186 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187 this.CodePage437Map, this.DirectToFontMap ];
188 this.savedValid = [ ];
189 this.respondString = '';
190 this.statusString = '';
191 this.internalClipboard = undefined;
195 VT100.prototype.reset = function(clearHistory) {
196 this.isEsc = 0 /* ESnormal */;
197 this.needWrap = false;
198 this.autoWrapMode = true;
199 this.dispCtrl = false;
200 this.toggleMeta = false;
201 this.insertMode = false;
202 this.applKeyMode = false;
203 this.cursorKeyMode = false;
204 this.crLfMode = false;
205 this.offsetMode = false;
206 this.mouseReporting = false;
207 this.utfEnabled = true;
208 this.visualBell = typeof suppressAllAudio !=
214 this.attr = 0x00F0 /* ATTR_DEFAULT */;
216 this.GMap = [ this.Latin1Map,
217 this.VT100GraphicsMap,
219 this.DirectToFontMap ];
220 this.translate = this.GMap[this.useGMap];
222 this.bottom = this.terminalHeight;
223 this.lastCharacter = ' ';
224 this.userTabStop = [ ];
227 for (var i = 0; i < 2; i++) {
228 while (this.console[i].firstChild) {
229 this.console[i].removeChild(this.console[i].firstChild);
234 this.enableAlternateScreen(false);
237 this.isInverted = false;
238 this.refreshInvertedState();
239 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.style);
242 VT100.prototype.initializeAnsiColors = function() {
243 var elem = document.createElement('pre');
244 this.container.appendChild(elem);
245 this.setTextContent(elem, ' ');
247 for (var i = 0; i < 16; i++) {
248 elem.id = 'ansi' + i;
249 this.ansi[i] = this.getCurrentComputedStyle(elem, 'backgroundColor');
251 this.container.removeChild(elem);
254 VT100.prototype.addListener = function(elem, event, listener) {
255 if (elem.addEventListener) {
256 elem.addEventListener(event, listener, false);
258 elem.attachEvent('on' + event, listener);
262 VT100.prototype.initializeUserCSSStyles = function() {
263 this.usercssActions = [];
264 if (typeof userCSSList != 'undefined') {
267 var wasSingleSel = 1;
268 var beginOfGroup = 0;
269 for (var i = 0; i <= userCSSList.length; ++i) {
270 if (i < userCSSList.length) {
271 var label = userCSSList[i][0];
272 var newGroup = userCSSList[i][1];
273 var enabled = userCSSList[i][2];
275 // Add user style sheet to document
276 var style = document.createElement('link');
277 var id = document.createAttribute('id');
278 id.nodeValue = 'usercss-' + i;
279 style.setAttributeNode(id);
280 var rel = document.createAttribute('rel');
281 rel.nodeValue = 'stylesheet';
282 style.setAttributeNode(rel);
283 var href = document.createAttribute('href');
284 href.nodeValue = 'usercss-' + i + '.css';
285 style.setAttributeNode(href);
286 var type = document.createAttribute('type');
287 type.nodeValue = 'text/css';
288 style.setAttributeNode(type);
289 document.getElementsByTagName('head')[0].appendChild(style);
290 style.disabled = !enabled;
294 if (newGroup || i == userCSSList.length) {
295 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
296 // The last group had multiple entries that are mutually exclusive;
297 // or the previous to last group did. In either case, we need to
298 // append a "<hr />" before we can add the last group to the menu.
301 wasSingleSel = i - beginOfGroup < 1;
305 for (var j = beginOfGroup; j < i; ++j) {
306 this.usercssActions[this.usercssActions.length] =
307 function(vt100, current, begin, count) {
309 // Deselect all other entries in the group, then either select
310 // (for multiple entries in group) or toggle (for on/off entry)
311 // the current entry.
313 var entry = vt100.getChildById(vt100.menu,
317 for (var c = count; c > 0; ++j) {
318 if (entry.tagName == 'LI') {
321 var label = vt100.usercss.childNodes[j];
323 label.innerHTML.replace(/^\u2714 /, '');
324 var sheet = document.getElementById(
328 sheet.disabled = !sheet.disabled;
330 sheet.disabled = false;
332 if (!sheet.disabled) {
333 label.innerHTML= '✔ ' + label.innerHTML;
336 sheet.disabled = true;
340 entry = entry.nextSibling;
343 }(this, j, beginOfGroup, i - beginOfGroup);
346 if (i == userCSSList.length) {
352 // Collect all entries in a group, before attaching them to the menu.
353 // This is necessary as we don't know whether this is a group of
354 // mutually exclusive options (which should be separated by "<hr />" on
355 // both ends), or whether this is a on/off toggle, which can be grouped
356 // together with other on/off options.
358 '<li>' + (enabled ? '✔ ' : '') + label + '</li>';
360 this.usercss.innerHTML = menu;
364 VT100.prototype.initializeElements = function(container) {
365 // If the necessary objects have not already been defined in the HTML
366 // page, create them now.
368 this.container = container;
369 } else if (!(this.container = document.getElementById('vt100'))) {
370 this.container = document.createElement('div');
371 this.container.id = 'vt100';
372 document.body.appendChild(this.container);
375 if (!this.getChildById(this.container, 'reconnect') ||
376 !this.getChildById(this.container, 'menu') ||
377 !this.getChildById(this.container, 'scrollable') ||
378 !this.getChildById(this.container, 'console') ||
379 !this.getChildById(this.container, 'alt_console') ||
380 !this.getChildById(this.container, 'ieprobe') ||
381 !this.getChildById(this.container, 'padding') ||
382 !this.getChildById(this.container, 'cursor') ||
383 !this.getChildById(this.container, 'lineheight') ||
384 !this.getChildById(this.container, 'usercss') ||
385 !this.getChildById(this.container, 'space') ||
386 !this.getChildById(this.container, 'input') ||
387 !this.getChildById(this.container, 'cliphelper') ||
388 !this.getChildById(this.container, 'attrib')) {
389 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
390 // we might get a pointless warning that a suitable plugin is not yet
391 // installed. If in doubt, we'd rather just stay silent.
394 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
396 embed = typeof suppressAllAudio != 'undefined' &&
397 suppressAllAudio ? "" :
398 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
401 'autostart="false" ' +
403 'enablejavascript="true" ' +
404 'type="audio/x-wav" ' +
407 'style="position:absolute;left:-1000px;top:-1000px" />';
412 this.container.innerHTML =
413 '<div id="reconnect" style="visibility: hidden">' +
414 '<input type="button" value="Connect" ' +
415 'onsubmit="return false" />' +
417 '<div id="cursize" style="visibility: hidden">' +
419 '<div id="menu"></div>' +
420 '<div id="scrollable">' +
421 '<pre id="lineheight"> </pre>' +
422 '<pre id="console">' +
424 '<div id="ieprobe"><span> </span></div>' +
426 '<pre id="alt_console" style="display: none"></pre>' +
427 '<div id="padding"></div>' +
428 '<pre id="cursor"> </pre>' +
430 '<div class="hidden">' +
431 '<div id="usercss"></div>' +
432 '<pre><div><span id="space"></span></div></pre>' +
433 '<input type="textfield" id="input" />' +
434 '<input type="textfield" id="cliphelper" />' +
435 '<span id="attrib"> </span>' +
436 (typeof suppressAllAudio != 'undefined' &&
437 suppressAllAudio ? "" :
438 embed + '<bgsound id="beep_bgsound" loop=1 />') +
442 // Find the object used for playing the "beep" sound, if any.
443 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
444 this.beeper = undefined;
446 this.beeper = this.getChildById(this.container,
448 if (!this.beeper || !this.beeper.Play) {
449 this.beeper = this.getChildById(this.container,
451 if (!this.beeper || typeof this.beeper.src == 'undefined') {
452 this.beeper = undefined;
457 // Initialize the variables for finding the text console and the
459 this.reconnectBtn = this.getChildById(this.container,'reconnect');
460 this.curSizeBox = this.getChildById(this.container, 'cursize');
461 this.menu = this.getChildById(this.container, 'menu');
462 this.scrollable = this.getChildById(this.container,
464 this.lineheight = this.getChildById(this.container,
467 [ this.getChildById(this.container, 'console'),
468 this.getChildById(this.container, 'alt_console') ];
469 var ieProbe = this.getChildById(this.container, 'ieprobe');
470 this.padding = this.getChildById(this.container, 'padding');
471 this.cursor = this.getChildById(this.container, 'cursor');
472 this.usercss = this.getChildById(this.container, 'usercss');
473 this.space = this.getChildById(this.container, 'space');
474 this.input = this.getChildById(this.container, 'input');
475 this.cliphelper = this.getChildById(this.container,
477 this.attributeHelper = this.getChildById(this.container, 'attrib');
479 // Add any user selectable style sheets to the menu
480 this.initializeUserCSSStyles();
482 // Remember the dimensions of a standard character glyph. We would
483 // expect that we could just check cursor.clientWidth/Height at any time,
484 // but it turns out that browsers sometimes invalidate these values
485 // (e.g. while displaying a print preview screen).
486 this.cursorWidth = this.cursor.clientWidth;
487 this.cursorHeight = this.lineheight.clientHeight;
489 // IE has a slightly different boxing model, that we need to compensate for
490 this.isIE = ieProbe.offsetTop > 1;
492 this.console.innerHTML = '';
494 // Determine if the terminal window is positioned at the beginning of the
495 // page, or if it is embedded somewhere else in the page. For full-screen
496 // terminals, automatically resize whenever the browser window changes.
497 var marginTop = parseInt(this.getCurrentComputedStyle(
498 document.body, 'marginTop'));
499 var marginLeft = parseInt(this.getCurrentComputedStyle(
500 document.body, 'marginLeft'));
501 var marginRight = parseInt(this.getCurrentComputedStyle(
502 document.body, 'marginRight'));
503 var x = this.container.offsetLeft;
504 var y = this.container.offsetTop;
505 for (var parent = this.container; parent = parent.offsetParent; ) {
506 x += parent.offsetLeft;
507 y += parent.offsetTop;
509 this.isEmbedded = marginTop != y ||
511 (window.innerWidth ||
512 document.documentElement.clientWidth ||
513 document.body.clientWidth) -
514 marginRight != x + this.container.offsetWidth;
515 if (!this.isEmbedded) {
516 // Some browsers generate resize events when the terminal is first
517 // shown. Disable showing the size indicator until a little bit after
518 // the terminal has been rendered the first time.
519 this.indicateSize = false;
520 setTimeout(function(vt100) {
522 vt100.indicateSize = true;
525 this.addListener(window, 'resize',
528 vt100.hideContextMenu();
530 vt100.showCurrentSize();
534 // Hide extra scrollbars attached to window
535 document.body.style.margin = '0px';
536 try { document.body.style.overflow ='hidden'; } catch (e) { }
537 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
541 this.hideContextMenu();
543 // Add listener to reconnect button
544 this.addListener(this.reconnectBtn.firstChild, 'click',
547 var rc = vt100.reconnect();
553 // Add input listeners
554 this.addListener(this.input, 'blur',
556 return function() { vt100.blurCursor(); } }(this));
557 this.addListener(this.input, 'focus',
559 return function() { vt100.focusCursor(); } }(this));
560 this.addListener(this.input, 'keydown',
563 if (!e) e = window.event;
564 return vt100.keyDown(e); } }(this));
565 this.addListener(this.input, 'keypress',
568 if (!e) e = window.event;
569 return vt100.keyPressed(e); } }(this));
570 this.addListener(this.input, 'keyup',
573 if (!e) e = window.event;
574 return vt100.keyUp(e); } }(this));
576 // Attach listeners that move the focus to the <input> field. This way we
577 // can make sure that we can receive keyboard input.
578 var mouseEvent = function(vt100, type) {
580 if (!e) e = window.event;
581 return vt100.mouseEvent(e, type);
584 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
585 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
586 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
588 // Initialize the blank terminal window.
589 this.currentScreen = 0;
592 this.numScrollbackLines = 0;
594 this.bottom = 0x7FFFFFFF;
600 VT100.prototype.getChildById = function(parent, id) {
601 var nodeList = parent.all || parent.getElementsByTagName('*');
602 if (typeof nodeList.namedItem == 'undefined') {
603 for (var i = 0; i < nodeList.length; i++) {
604 if (nodeList[i].id == id) {
610 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
611 return elem ? elem[0] || elem : null;
615 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
616 if (typeof elem.currentStyle != 'undefined') {
617 return elem.currentStyle[style];
619 return document.defaultView.getComputedStyle(elem, null)[style];
623 VT100.prototype.reconnect = function() {
627 VT100.prototype.showReconnect = function(state) {
629 this.reconnectBtn.style.visibility = '';
631 this.reconnectBtn.style.visibility = 'hidden';
635 VT100.prototype.repairElements = function(console) {
636 for (var line = console.firstChild; line; line = line.nextSibling) {
637 if (!line.clientHeight) {
638 var newLine = document.createElement(line.tagName);
639 newLine.style.cssText = line.style.cssText;
640 newLine.className = line.className;
641 if (line.tagName == 'DIV') {
642 for (var span = line.firstChild; span; span = span.nextSibling) {
643 var newSpan = document.createElement(span.tagName);
644 newSpan.style.cssText = span.style.cssText;
645 this.setTextContent(newSpan, this.getTextContent(span));
646 newLine.appendChild(newSpan);
649 this.setTextContent(newLine, this.getTextContent(line));
651 line.parentNode.replaceChild(newLine, line);
657 VT100.prototype.resized = function(w, h) {
660 VT100.prototype.resizer = function() {
661 // The cursor can get corrupted if the print-preview is displayed in Firefox.
662 // Recreating it, will repair it.
663 var newCursor = document.createElement('pre');
664 this.setTextContent(newCursor, ' ');
665 newCursor.id = 'cursor';
666 newCursor.style.cssText = this.cursor.style.cssText;
667 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
668 if (!newCursor.clientHeight) {
669 // Things are broken right now. This is probably because we are
670 // displaying the print-preview. Just don't change any of our settings
671 // until the print dialog is closed again.
672 newCursor.parentNode.removeChild(newCursor);
675 // Swap the old broken cursor for the newly created one.
676 this.cursor.parentNode.removeChild(this.cursor);
677 this.cursor = newCursor;
680 // Really horrible things happen if the contents of the terminal changes
681 // while the print-preview is showing. We get HTML elements that show up
682 // in the DOM, but that do not take up any space. Find these elements and
684 this.repairElements(this.console[0]);
685 this.repairElements(this.console[1]);
687 // Lock the cursor size to the size of a normal character. This helps with
688 // characters that are taller/shorter than normal. Unfortunately, we will
689 // still get confused if somebody enters a character that is wider/narrower
690 // than normal. This can happen if the browser tries to substitute a
691 // characters from a different font.
692 this.cursor.style.width = this.cursorWidth + 'px';
693 this.cursor.style.height = this.cursorHeight + 'px';
695 // Adjust height for one pixel padding of the #vt100 element.
696 // The latter is necessary to properly display the inactive cursor.
697 var console = this.console[this.currentScreen];
698 var height = (this.isEmbedded ? this.container.clientHeight
699 : (window.innerHeight ||
700 document.documentElement.clientHeight ||
701 document.body.clientHeight))-1;
702 var partial = height % this.cursorHeight;
703 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
704 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
705 var oldTerminalHeight = this.terminalHeight;
709 // Clip the cursor to the visible screen.
710 var cx = this.cursorX;
711 var cy = this.cursorY + this.numScrollbackLines;
713 // The alternate screen never keeps a scroll back buffer.
714 this.updateNumScrollbackLines();
715 while (this.currentScreen && this.numScrollbackLines > 0) {
716 console.removeChild(console.firstChild);
717 this.numScrollbackLines--;
719 cy -= this.numScrollbackLines;
722 } else if (cx > this.terminalWidth) {
723 cx = this.terminalWidth - 1;
730 } else if (cy > this.terminalHeight) {
731 cy = this.terminalHeight - 1;
737 // Clip the scroll region to the visible screen.
738 if (this.bottom > this.terminalHeight ||
739 this.bottom == oldTerminalHeight) {
740 this.bottom = this.terminalHeight;
742 if (this.top >= this.bottom) {
743 this.top = this.bottom-1;
749 // Truncate lines, if necessary. Explicitly reposition cursor (this is
750 // particularly important after changing the screen number), and reset
751 // the scroll region to the default.
752 this.truncateLines(this.terminalWidth);
753 this.putString(cx, cy, '', undefined);
754 this.scrollable.scrollTop = this.numScrollbackLines *
755 this.cursorHeight + 1;
757 // Update classNames for lines in the scrollback buffer
758 var line = console.firstChild;
759 for (var i = 0; i < this.numScrollbackLines; i++) {
760 line.className = 'scrollback';
761 line = line.nextSibling;
765 line = line.nextSibling;
768 // Reposition the reconnect button
769 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
770 this.reconnectBtn.clientWidth)/2 + 'px';
771 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
772 this.reconnectBtn.clientHeight)/2 + 'px';
774 // Send notification that the window size has been changed
775 this.resized(this.terminalWidth, this.terminalHeight);
778 VT100.prototype.showCurrentSize = function() {
779 if (!this.indicateSize) {
782 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
784 this.curSizeBox.style.left =
785 (this.terminalWidth*this.cursorWidth -
786 this.curSizeBox.clientWidth)/2 + 'px';
787 this.curSizeBox.style.top =
788 (this.terminalHeight*this.cursorHeight -
789 this.curSizeBox.clientHeight)/2 + 'px';
790 this.curSizeBox.style.visibility = '';
791 if (this.curSizeTimeout) {
792 clearTimeout(this.curSizeTimeout);
795 // Only show the terminal size for a short amount of time after resizing.
796 // Then hide this information, again. Some browsers generate resize events
797 // throughout the entire resize operation. This is nice, and we will show
798 // the terminal size while the user is dragging the window borders.
799 // Other browsers only generate a single event when the user releases the
800 // mouse. In those cases, we can only show the terminal size once at the
801 // end of the resize operation.
802 this.curSizeTimeout = setTimeout(function(vt100) {
804 vt100.curSizeTimeout = null;
805 vt100.curSizeBox.style.visibility = 'hidden';
810 VT100.prototype.selection = function() {
812 return '' + (window.getSelection && window.getSelection() ||
813 document.selection && document.selection.type == 'Text' &&
814 document.selection.createRange().text || '');
820 VT100.prototype.cancelEvent = function(event) {
822 // For non-IE browsers
823 event.stopPropagation();
824 event.preventDefault();
829 event.cancelBubble = true;
830 event.returnValue = false;
838 VT100.prototype.mouseEvent = function(event, type) {
839 // If any text is currently selected, do not move the focus as that would
840 // invalidate the selection.
841 var selection = this.selection();
842 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
846 // Compute mouse position in characters.
847 var offsetX = this.container.offsetLeft;
848 var offsetY = this.container.offsetTop;
849 for (var e = this.container; e = e.offsetParent; ) {
850 offsetX += e.offsetLeft;
851 offsetY += e.offsetTop;
853 var x = (event.clientX - offsetX) / this.cursorWidth;
854 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
855 this.cursorHeight - this.numScrollbackLines;
857 if (x >= this.terminalWidth) {
858 x = this.terminalWidth - 1;
865 if (y >= this.terminalHeight) {
866 y = this.terminalHeight - 1;
874 // Compute button number and modifier keys.
875 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
876 typeof event.pageX != 'undefined' ? event.button :
877 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
878 if (button != undefined) {
879 if (event.shiftKey) {
882 if (event.altKey || event.metaKey) {
890 // Report mouse events if they happen inside of the current screen and
891 // with the SHIFT key unpressed. Both of these restrictions do not apply
892 // for button releases, as we always want to report those.
893 if (this.mouseReporting && !selection.length &&
894 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
895 if (inside || type != 0 /* MOUSE_DOWN */) {
896 if (button != undefined) {
897 var report = '\u001B[M' + String.fromCharCode(button + 32) +
898 String.fromCharCode(x + 33) +
899 String.fromCharCode(y + 33);
900 if (type != 2 /* MOUSE_CLICK */) {
901 this.keysPressed(report);
904 // If we reported the event, stop propagating it (not sure, if this
905 // actually works on most browsers; blocking the global "oncontextmenu"
906 // even is still necessary).
907 return this.cancelEvent(event);
912 // Bring up context menu.
913 if (button == 2 && !event.shiftKey) {
914 if (type == 0 /* MOUSE_DOWN */) {
915 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
917 return this.cancelEvent(event);
920 if (this.mouseReporting) {
922 event.shiftKey = false;
930 VT100.prototype.replaceChar = function(s, ch, repl) {
932 i = s.indexOf(ch, i + 1);
936 s = s.substr(0, i) + repl + s.substr(i + 1);
941 VT100.prototype.htmlEscape = function(s) {
942 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
943 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
946 VT100.prototype.getTextContent = function(elem) {
947 return elem.textContent ||
948 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
951 VT100.prototype.setTextContent = function(elem, s) {
952 // Check if we find any URLs in the text. If so, automatically convert them
954 if (this.urlRE && this.urlRE.test(s)) {
958 if (RegExp.leftContext != null) {
959 inner += this.htmlEscape(RegExp.leftContext);
960 consumed += RegExp.leftContext.length;
962 var url = this.htmlEscape(RegExp.lastMatch);
965 // If no protocol was specified, try to guess a reasonable one.
966 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
967 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
968 var slash = url.indexOf('/');
969 var at = url.indexOf('@');
970 var question = url.indexOf('?');
972 (at < question || question < 0) &&
973 (slash < 0 || (question > 0 && slash > question))) {
974 fullUrl = 'mailto:' + url;
976 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
981 inner += '<a target="vt100Link" href="' + fullUrl +
983 consumed += RegExp.lastMatch.length;
984 s = s.substr(consumed);
985 if (!this.urlRE.test(s)) {
986 if (RegExp.rightContext != null) {
987 inner += this.htmlEscape(RegExp.rightContext);
992 elem.innerHTML = inner;
996 // Updating the content of an element is an expensive operation. It actually
997 // pays off to first check whether the element is still unchanged.
998 if (typeof elem.textContent == 'undefined') {
999 if (elem.innerText != s) {
1003 // Very old versions of IE do not allow setting innerText. Instead,
1004 // remove all children, by setting innerHTML and then set the text
1005 // using DOM methods.
1006 elem.innerHTML = '';
1007 elem.appendChild(document.createTextNode(
1008 this.replaceChar(s, ' ', '\u00A0')));
1012 if (elem.textContent != s) {
1013 elem.textContent = s;
1018 VT100.prototype.insertBlankLine = function(y, style) {
1019 // Insert a blank line a position y. This method ignores the scrollback
1020 // buffer. The caller has to add the length of the scrollback buffer to
1021 // the position, if necessary.
1022 // If the position is larger than the number of current lines, this
1023 // method just adds a new line right after the last existing one. It does
1024 // not add any missing lines in between. It is the caller's responsibility
1026 if (style == undefined) {
1031 line = document.createElement('pre');
1032 this.setTextContent(line, '\n');
1034 line = document.createElement('div');
1035 var span = document.createElement('span');
1036 span.style.cssText = style;
1037 this.setTextContent(span, this.spaces(this.terminalWidth));
1038 line.appendChild(span);
1040 line.style.height = this.cursorHeight + 'px';
1041 var console = this.console[this.currentScreen];
1042 if (console.childNodes.length > y) {
1043 console.insertBefore(line, console.childNodes[y]);
1045 console.appendChild(line);
1049 VT100.prototype.updateWidth = function() {
1050 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1052 return this.terminalWidth;
1055 VT100.prototype.updateHeight = function() {
1056 // We want to be able to display either a terminal window that fills the
1057 // entire browser window, or a terminal window that is contained in a
1058 // <div> which is embededded somewhere in the web page.
1059 if (this.isEmbedded) {
1060 // Embedded terminal. Use size of the containing <div> (id="vt100").
1061 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1064 // Use the full browser window.
1065 this.terminalHeight = Math.floor(((window.innerHeight ||
1066 document.documentElement.clientHeight ||
1067 document.body.clientHeight)-1)/
1070 return this.terminalHeight;
1073 VT100.prototype.updateNumScrollbackLines = function() {
1074 var scrollback = Math.floor(
1075 this.console[this.currentScreen].offsetHeight /
1076 this.cursorHeight) -
1077 this.terminalHeight;
1078 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1079 return this.numScrollbackLines;
1082 VT100.prototype.truncateLines = function(width) {
1086 for (var line = this.console[this.currentScreen].firstChild; line;
1087 line = line.nextSibling) {
1088 if (line.tagName == 'DIV') {
1091 // Traverse current line and truncate it once we saw "width" characters
1092 for (var span = line.firstChild; span;
1093 span = span.nextSibling) {
1094 var s = this.getTextContent(span);
1096 if (x + l > width) {
1097 this.setTextContent(span, s.substr(0, width - x));
1098 while (span.nextSibling) {
1099 line.removeChild(line.lastChild);
1105 // Prune white space from the end of the current line
1106 var span = line.lastChild;
1107 while (span && !span.style.cssText.length) {
1108 // Scan backwards looking for first non-space character
1109 var s = this.getTextContent(span);
1110 for (var i = s.length; i--; ) {
1111 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1112 if (i+1 != s.length) {
1113 this.setTextContent(s.substr(0, i+1));
1121 span = span.previousSibling;
1123 // Remove blank <span>'s from end of line
1124 line.removeChild(sibling);
1126 // Remove entire line (i.e. <div>), if empty
1127 var blank = document.createElement('pre');
1128 blank.style.height = this.cursorHeight + 'px';
1129 this.setTextContent(blank, '\n');
1130 line.parentNode.replaceChild(blank, line);
1138 VT100.prototype.putString = function(x, y, text, style) {
1142 var yIdx = y + this.numScrollbackLines;
1148 var console = this.console[this.currentScreen];
1149 if (!text.length && (yIdx >= console.childNodes.length ||
1150 console.childNodes[yIdx].tagName != 'DIV')) {
1151 // Positioning cursor to a blank location
1154 // Create missing blank lines at end of page
1155 while (console.childNodes.length <= yIdx) {
1156 // In order to simplify lookups, we want to make sure that each line
1157 // is represented by exactly one element (and possibly a whole bunch of
1159 // For non-blank lines, we can create a <div> containing one or more
1160 // <span>s. For blank lines, this fails as browsers tend to optimize them
1161 // away. But fortunately, a <pre> tag containing a newline character
1162 // appears to work for all browsers (a would also work, but then
1163 // copying from the browser window would insert superfluous spaces into
1165 this.insertBlankLine(yIdx);
1167 line = console.childNodes[yIdx];
1169 // If necessary, promote blank '\n' line to a <div> tag
1170 if (line.tagName != 'DIV') {
1171 var div = document.createElement('div');
1172 div.style.height = this.cursorHeight + 'px';
1173 div.innerHTML = '<span></span>';
1174 console.replaceChild(div, line);
1178 // Scan through list of <span>'s until we find the one where our text
1180 span = line.firstChild;
1182 while (span.nextSibling && xPos < x) {
1183 len = this.getTextContent(span).length;
1184 if (xPos + len > x) {
1188 span = span.nextSibling;
1192 // If current <span> is not long enough, pad with spaces or add new
1194 s = this.getTextContent(span);
1195 var oldStyle = span.style.cssText;
1196 if (xPos + s.length < x) {
1197 if (oldStyle != '') {
1198 span = document.createElement('span');
1199 line.appendChild(span);
1200 span.style.cssText = '';
1207 } while (xPos + s.length < x);
1210 // If styles do not match, create a new <span>
1211 var del = text.length - s.length + x - xPos;
1212 if (oldStyle != style && (oldStyle || style)) {
1214 // Replacing text at beginning of existing <span>
1215 if (text.length >= s.length) {
1216 // New text is equal or longer than existing text
1219 // Insert new <span> before the current one, then remove leading
1220 // part of existing <span>, adjust style of new <span>, and finally
1222 sibling = document.createElement('span');
1223 line.insertBefore(sibling, span);
1224 this.setTextContent(span, s.substr(text.length));
1229 // Replacing text some way into the existing <span>
1230 var remainder = s.substr(x + text.length - xPos);
1231 this.setTextContent(span, s.substr(0, x - xPos));
1233 sibling = document.createElement('span');
1234 if (span.nextSibling) {
1235 line.insertBefore(sibling, span.nextSibling);
1237 if (remainder.length) {
1238 sibling = document.createElement('span');
1239 sibling.style.cssText = oldStyle;
1240 this.setTextContent(sibling, remainder);
1241 line.insertBefore(sibling, span.nextSibling);
1244 line.appendChild(sibling);
1246 if (remainder.length) {
1247 sibling = document.createElement('span');
1248 sibling.style.cssText = oldStyle;
1249 this.setTextContent(sibling, remainder);
1250 line.appendChild(sibling);
1255 span.style.cssText = style;
1257 // Overwrite (partial) <span> with new text
1258 s = s.substr(0, x - xPos) +
1260 s.substr(x + text.length - xPos);
1262 this.setTextContent(span, s);
1265 // Delete all subsequent <span>'s that have just been overwritten
1266 sibling = span.nextSibling;
1267 while (del > 0 && sibling) {
1268 s = this.getTextContent(sibling);
1271 line.removeChild(sibling);
1273 sibling = span.nextSibling;
1275 this.setTextContent(sibling, s.substr(del));
1280 // Merge <span> with next sibling, if styles are identical
1281 if (sibling && span.style.cssText == sibling.style.cssText) {
1282 this.setTextContent(span,
1283 this.getTextContent(span) +
1284 this.getTextContent(sibling));
1285 line.removeChild(sibling);
1291 this.cursorX = x + text.length;
1292 if (this.cursorX >= this.terminalWidth) {
1293 this.cursorX = this.terminalWidth - 1;
1294 if (this.cursorX < 0) {
1300 if (!this.cursor.style.visibility) {
1301 var idx = this.cursorX - xPos;
1303 // If we are in a non-empty line, take the cursor Y position from the
1304 // other elements in this line. If dealing with broken, non-proportional
1305 // fonts, this is likely to yield better results.
1306 pixelY = span.offsetTop +
1307 span.offsetParent.offsetTop;
1308 s = this.getTextContent(span);
1309 var nxtIdx = idx - s.length;
1311 this.setTextContent(this.cursor, s.charAt(idx));
1312 pixelX = span.offsetLeft +
1313 idx*span.offsetWidth / s.length;
1316 pixelX = span.offsetLeft + span.offsetWidth;
1318 if (span.nextSibling) {
1319 s = this.getTextContent(span.nextSibling);
1320 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1322 pixelX = span.nextSibling.offsetLeft +
1323 nxtIdx*span.offsetWidth / s.length;
1326 this.setTextContent(this.cursor, ' ');
1330 this.setTextContent(this.cursor, ' ');
1334 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
1336 this.setTextContent(this.space, this.spaces(this.cursorX));
1337 this.cursor.style.left = this.space.offsetWidth +
1338 console.offsetLeft + 'px';
1340 this.cursorY = yIdx - this.numScrollbackLines;
1342 this.cursor.style.top = pixelY + 'px';
1344 this.cursor.style.top = yIdx*this.cursorHeight +
1345 console.offsetTop + 'px';
1349 // Merge <span> with previous sibling, if styles are identical
1350 if ((sibling = span.previousSibling) &&
1351 span.style.cssText == sibling.style.cssText) {
1352 this.setTextContent(span,
1353 this.getTextContent(sibling) +
1354 this.getTextContent(span));
1355 line.removeChild(sibling);
1358 // Prune white space from the end of the current line
1359 span = line.lastChild;
1360 while (span && !span.style.cssText.length) {
1361 // Scan backwards looking for first non-space character
1362 s = this.getTextContent(span);
1363 for (var i = s.length; i--; ) {
1364 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1365 if (i+1 != s.length) {
1366 this.setTextContent(s.substr(0, i+1));
1374 span = span.previousSibling;
1376 // Remove blank <span>'s from end of line
1377 line.removeChild(sibling);
1379 // Remove entire line (i.e. <div>), if empty
1380 var blank = document.createElement('pre');
1381 blank.style.height = this.cursorHeight + 'px';
1382 this.setTextContent(blank, '\n');
1383 line.parentNode.replaceChild(blank, line);
1390 VT100.prototype.gotoXY = function(x, y) {
1391 if (x >= this.terminalWidth) {
1392 x = this.terminalWidth - 1;
1398 if (this.offsetMode) {
1403 maxY = this.terminalHeight;
1411 this.putString(x, y, '', undefined);
1412 this.needWrap = false;
1415 VT100.prototype.gotoXaY = function(x, y) {
1416 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1419 VT100.prototype.refreshInvertedState = function() {
1420 if (this.isInverted) {
1421 this.scrollable.style.color = this.ansi[15];
1422 this.scrollable.style.backgroundColor = this.ansi[0];
1424 this.scrollable.style.color = '';
1425 this.scrollable.style.backgroundColor = '';
1429 VT100.prototype.enableAlternateScreen = function(state) {
1430 // Don't do anything, if we are already on the desired screen
1431 if ((state ? 1 : 0) == this.currentScreen) {
1432 // Calling the resizer is not actually necessary. But it is a good way
1433 // of resetting state that might have gotten corrupted.
1438 // We save the full state of the normal screen, when we switch away from it.
1439 // But for the alternate screen, no saving is necessary. We always reset
1440 // it when we switch to it.
1445 // Display new screen, and initialize state (the resizer does that for us).
1446 this.currentScreen = state ? 1 : 0;
1447 this.console[1-this.currentScreen].style.display = 'none';
1448 this.console[this.currentScreen].style.display = '';
1451 // If we switched to the alternate screen, reset it completely. Otherwise,
1452 // restore the saved state.
1455 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1457 this.restoreCursor();
1461 VT100.prototype.hideCursor = function() {
1462 var hidden = this.cursor.style.visibility == 'hidden';
1464 this.cursor.style.visibility = 'hidden';
1470 VT100.prototype.showCursor = function(x, y) {
1471 if (this.cursor.style.visibility) {
1472 this.cursor.style.visibility = '';
1473 this.putString(x == undefined ? this.cursorX : x,
1474 y == undefined ? this.cursorY : y,
1481 VT100.prototype.scrollBack = function() {
1482 var i = this.scrollable.scrollTop -
1483 this.scrollable.clientHeight;
1484 this.scrollable.scrollTop = i < 0 ? 0 : i;
1487 VT100.prototype.scrollFore = function() {
1488 var i = this.scrollable.scrollTop +
1489 this.scrollable.clientHeight;
1490 this.scrollable.scrollTop = i > this.numScrollbackLines *
1491 this.cursorHeight + 1
1492 ? this.numScrollbackLines *
1493 this.cursorHeight + 1
1497 VT100.prototype.spaces = function(i) {
1505 VT100.prototype.clearRegion = function(x, y, w, h, style) {
1510 if (w > this.terminalWidth) {
1511 w = this.terminalWidth;
1513 if ((w -= x) <= 0) {
1520 if (h > this.terminalHeight) {
1521 h = this.terminalHeight;
1523 if ((h -= y) <= 0) {
1527 // Special case the situation where we clear the entire screen, and we do
1528 // not have a scrollback buffer. In that case, we should just remove all
1530 if (!this.numScrollbackLines &&
1531 w == this.terminalWidth && h == this.terminalHeight &&
1533 var console = this.console[this.currentScreen];
1534 while (console.lastChild) {
1535 console.removeChild(console.lastChild);
1537 this.putString(this.cursorX, this.cursorY, '', undefined);
1539 var hidden = this.hideCursor();
1540 var cx = this.cursorX;
1541 var cy = this.cursorY;
1542 var s = this.spaces(w);
1543 for (var i = y+h; i-- > y; ) {
1544 this.putString(x, i, s, style);
1546 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1550 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1553 var console = this.console[this.currentScreen];
1554 if (sY >= console.childNodes.length) {
1555 text[0] = this.spaces(w);
1558 var line = console.childNodes[sY];
1559 if (line.tagName != 'DIV' || !line.childNodes.length) {
1560 text[0] = this.spaces(w);
1564 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1565 var s = this.getTextContent(span);
1568 var o = sX > x ? sX - x : 0;
1569 text[text.length] = s.substr(o, w);
1570 style[style.length] = span.style.cssText;
1576 text[text.length] = this.spaces(w);
1577 style[style.length] = null;
1581 var hidden = this.hideCursor();
1582 var cx = this.cursorX;
1583 var cy = this.cursorY;
1584 for (var i = 0; i < text.length; i++) {
1585 this.putString(dX, dY - this.numScrollbackLines, text[i], style[i]);
1586 dX += text[i].length;
1588 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1591 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY, style) {
1592 var left = incX < 0 ? -incX : 0;
1593 var right = incX > 0 ? incX : 0;
1594 var up = incY < 0 ? -incY : 0;
1595 var down = incY > 0 ? incY : 0;
1597 // Clip region against terminal size
1598 var dontScroll = null;
1603 if (w > this.terminalWidth - right) {
1604 w = this.terminalWidth - right;
1606 if ((w -= x) <= 0) {
1613 if (h > this.terminalHeight - down) {
1614 h = this.terminalHeight - down;
1620 if (style && style.indexOf('underline')) {
1621 // Different terminal emulators disagree on the attributes that
1622 // are used for scrolling. The consensus seems to be, never to
1623 // fill with underlined spaces. N.B. this is different from the
1624 // cases when the user blanks a region. User-initiated blanking
1625 // always fills with all of the current attributes.
1626 this.attributeHelper.cssText
1627 = style.replace(/text-decoration:underline;/, "");
1628 style = this.attributeHelper.cssText;
1631 // Compute current scroll position
1632 var scrollPos = this.numScrollbackLines -
1633 (this.scrollable.scrollTop-1) / this.cursorHeight;
1635 // Determine original cursor position. Hide cursor temporarily to avoid
1636 // visual artifacts.
1637 var hidden = this.hideCursor();
1638 var cx = this.cursorX;
1639 var cy = this.cursorY;
1640 var console = this.console[this.currentScreen];
1642 if (!incX && !x && w == this.terminalWidth) {
1643 // Scrolling entire lines
1646 if (!this.currentScreen && y == -incY &&
1647 h == this.terminalHeight + incY) {
1648 // Scrolling up with adding to the scrollback buffer. This is only
1649 // possible if there are at least as many lines in the console,
1650 // as the terminal is high
1651 while (console.childNodes.length < this.terminalHeight) {
1652 this.insertBlankLine(this.terminalHeight);
1655 // Add new lines at bottom in order to force scrolling
1656 for (var i = 0; i < y; i++) {
1657 this.insertBlankLine(console.childNodes.length, style);
1660 // Adjust the number of lines in the scrollback buffer by
1661 // removing excess entries.
1662 this.updateNumScrollbackLines();
1663 while (this.numScrollbackLines >
1664 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1665 console.removeChild(console.firstChild);
1666 this.numScrollbackLines--;
1669 // Mark lines in the scrollback buffer, so that they do not get
1671 for (var i = this.numScrollbackLines, j = -incY;
1672 i-- > 0 && j-- > 0; ) {
1673 console.childNodes[i].className = 'scrollback';
1676 // Scrolling up without adding to the scrollback buffer.
1679 console.childNodes.length >
1680 this.numScrollbackLines + y + incY; ) {
1681 console.removeChild(console.childNodes[
1682 this.numScrollbackLines + y + incY]);
1685 // If we used to have a scrollback buffer, then we must make sure
1686 // that we add back blank lines at the bottom of the terminal.
1687 // Similarly, if we are scrolling in the middle of the screen,
1688 // we must add blank lines to ensure that the bottom of the screen
1689 // does not move up.
1690 if (this.numScrollbackLines > 0 ||
1691 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1692 for (var i = -incY; i-- > 0; ) {
1693 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1702 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1703 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1705 for (var i = incY; i--; ) {
1706 this.insertBlankLine(this.numScrollbackLines + y, style);
1710 // Scrolling partial lines
1712 // Scrolling up or horizontally within a line
1713 for (var i = y + this.numScrollbackLines;
1714 i < y + this.numScrollbackLines + h;
1716 this.copyLineSegment(x + incX, i + incY, x, i, w);
1720 for (var i = y + this.numScrollbackLines + h;
1721 i-- > y + this.numScrollbackLines; ) {
1722 this.copyLineSegment(x + incX, i + incY, x, i, w);
1726 // Clear blank regions
1728 this.clearRegion(x, y, incX, h, style);
1729 } else if (incX < 0) {
1730 this.clearRegion(x + w + incX, y, -incX, h, style);
1733 this.clearRegion(x, y, w, incY, style);
1734 } else if (incY < 0) {
1735 this.clearRegion(x, y + h + incY, w, -incY, style);
1739 // Reset scroll position
1740 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1741 this.cursorHeight + 1;
1743 // Move cursor back to its original position
1744 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1748 VT100.prototype.copy = function(selection) {
1749 if (selection == undefined) {
1750 selection = this.selection();
1752 this.internalClipboard = undefined;
1753 if (selection.length) {
1756 this.cliphelper.value = selection;
1757 this.cliphelper.select();
1758 this.cliphelper.createTextRange().execCommand('copy');
1760 this.internalClipboard = selection;
1762 this.cliphelper.value = '';
1766 VT100.prototype.copyLast = function() {
1767 // Opening the context menu can remove the selection. We try to prevent this
1768 // from happening, but that is not possible for all browsers. So, instead,
1769 // we compute the selection before showing the menu.
1770 this.copy(this.lastSelection);
1773 VT100.prototype.pasteFnc = function() {
1774 var clipboard = undefined;
1775 if (this.internalClipboard != undefined) {
1776 clipboard = this.internalClipboard;
1779 this.cliphelper.value = '';
1780 this.cliphelper.createTextRange().execCommand('paste');
1781 clipboard = this.cliphelper.value;
1785 this.cliphelper.value = '';
1786 if (clipboard && this.menu.style.visibility == 'hidden') {
1788 this.keysPressed('' + clipboard);
1795 VT100.prototype.toggleUTF = function() {
1796 this.utfEnabled = !this.utfEnabled;
1799 VT100.prototype.toggleBell = function() {
1800 this.visualBell = !this.visualBell;
1803 VT100.prototype.about = function() {
1804 alert("VT100 Terminal Emulator " + "2.9 (revision 165)" +
1805 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1806 "For more information check http://shellinabox.com");
1809 VT100.prototype.hideContextMenu = function() {
1810 this.menu.style.visibility = 'hidden';
1811 this.menu.style.top = '-100px';
1812 this.menu.style.left = '-100px';
1813 this.menu.style.width = '0px';
1814 this.menu.style.height = '0px';
1817 VT100.prototype.extendContextMenu = function(entries, actions) {
1820 VT100.prototype.showContextMenu = function(x, y) {
1821 this.menu.innerHTML =
1822 '<table class="popup" ' +
1823 'cellpadding="0" cellspacing="0">' +
1825 '<ul id="menuentries">' +
1826 '<li id="beginclipboard">Copy</li>' +
1827 '<li id="endclipboard">Paste</li>' +
1829 '<li id="reset">Reset</li>' +
1831 '<li id="beginconfig">' +
1832 (this.utfEnabled ? '✔ ' : '') + 'Unicode</li>' +
1833 '<li id="endconfig">' +
1834 (this.visualBell ? '✔ ' : '') + 'Visual Bell</li>'+
1835 (this.usercss.firstChild ?
1836 '<hr id="beginusercss" />' +
1837 this.usercss.innerHTML +
1838 '<hr id="endusercss" />' :
1840 '<li id="about">About...</li>' +
1845 var popup = this.menu.firstChild;
1846 var menuentries = this.getChildById(popup, 'menuentries');
1848 // Determine menu entries that should be disabled
1849 this.lastSelection = this.selection();
1850 if (!this.lastSelection.length) {
1851 menuentries.firstChild.className
1854 var p = this.pasteFnc();
1856 menuentries.childNodes[1].className
1860 // Actions for default items
1861 var actions = [ this.copyLast, p, this.reset,
1862 this.toggleUTF, this.toggleBell ];
1864 // Actions for user CSS styles (if any)
1865 for (var i = 0; i < this.usercssActions.length; ++i) {
1866 actions[actions.length] = this.usercssActions[i];
1868 actions[actions.length] = this.about;
1870 // Allow subclasses to dynamically add entries to the context menu
1871 this.extendContextMenu(menuentries, actions);
1873 // Hook up event listeners
1874 for (var node = menuentries.firstChild, i = 0; node;
1875 node = node.nextSibling) {
1876 if (node.tagName == 'LI') {
1877 if (node.className != 'disabled') {
1878 this.addListener(node, 'mouseover',
1879 function(vt100, node) {
1881 node.className = 'hover';
1884 this.addListener(node, 'mouseout',
1885 function(vt100, node) {
1887 node.className = '';
1890 this.addListener(node, 'mousedown',
1891 function(vt100, action) {
1892 return function(event) {
1893 vt100.hideContextMenu();
1895 return vt100.cancelEvent(event || window.event);
1897 }(this, actions[i]));
1898 this.addListener(node, 'mouseup',
1900 return function(event) {
1901 return vt100.cancelEvent(event || window.event);
1904 this.addListener(node, 'mouseclick',
1906 return function(event) {
1907 return vt100.cancelEvent(event || window.event);
1915 // Position menu next to the mouse pointer
1916 if (x + popup.clientWidth > this.container.offsetWidth) {
1917 x = this.container.offsetWidth - popup.clientWidth;
1922 if (y + popup.clientHeight > this.container.offsetHeight) {
1923 y = this.container.offsetHeight-popup.clientHeight;
1928 popup.style.left = x + 'px';
1929 popup.style.top = y + 'px';
1931 // Block all other interactions with the terminal emulator
1932 this.menu.style.left = '0px';
1933 this.menu.style.top = '0px';
1934 this.menu.style.width = this.container.offsetWidth + 'px';
1935 this.menu.style.height = this.container.offsetHeight + 'px';
1936 this.addListener(this.menu, 'click', function(vt100) {
1938 vt100.hideContextMenu();
1943 this.menu.style.visibility = '';
1946 VT100.prototype.keysPressed = function(ch) {
1947 for (var i = 0; i < ch.length; i++) {
1948 var c = ch.charCodeAt(i);
1949 this.vt100(c >= 7 && c <= 15 ||
1950 c == 24 || c == 26 || c == 27 || c >= 32
1951 ? String.fromCharCode(c) : '<' + c + '>');
1955 VT100.prototype.handleKey = function(event) {
1957 if (typeof event.charCode != 'undefined') {
1958 // non-IE keypress events have a translated charCode value. Also, our
1959 // fake events generated when receiving keydown events include this data
1961 ch = event.charCode;
1962 key = event.keyCode;
1964 // When sending a keypress event, IE includes the translated character
1965 // code in the keyCode field.
1970 // Apply modifier keys (ctrl and shift)
1973 if (event.ctrlKey) {
1974 if (ch >= 32 && ch <= 127) {
1978 if (event.shiftKey) {
1979 if (ch >= 97 && ch <= 122) {
1983 if (ch >= 65 && ch <= 90) {
1992 // By this point, "ch" is either defined and contains the character code, or
1993 // it is undefined and "key" defines the code of a function key
1994 if (ch != undefined) {
1995 ch = String.fromCharCode(ch);
1996 this.scrollable.scrollTop = this.numScrollbackLines *
1997 this.cursorHeight + 1;
1999 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2000 // Many programs have difficulties dealing with parametrized escape
2001 // sequences for function keys. Thus, if ALT is the only modifier
2002 // key, return Emacs-style keycodes for commonly used keys.
2004 case 33: /* Page Up */ ch = '\u001B<'; break;
2005 case 34: /* Page Down */ ch = '\u001B>'; break;
2006 case 37: /* Left */ ch = '\u001Bb'; break;
2007 case 38: /* Up */ ch = '\u001Bp'; break;
2008 case 39: /* Right */ ch = '\u001Bf'; break;
2009 case 40: /* Down */ ch = '\u001Bn'; break;
2010 case 46: /* Delete */ ch = '\u001Bd'; break;
2013 } else if (event.shiftKey && !event.ctrlKey &&
2014 !event.altKey && !event.metaKey) {
2016 case 33: /* Page Up */ this.scrollBack(); return;
2017 case 34: /* Page Down */ this.scrollFore(); return;
2021 if (ch == undefined) {
2023 case 8: /* Backspace */ ch = '\u007f'; break;
2024 case 9: /* Tab */ ch = '\u0009'; break;
2025 case 10: /* Return */ ch = '\u000A'; break;
2026 case 13: /* Enter */ ch = this.crLfMode ?
2027 '\r\n' : '\r'; break;
2028 case 16: /* Shift */ return;
2029 case 17: /* Ctrl */ return;
2030 case 18: /* Alt */ return;
2031 case 19: /* Break */ return;
2032 case 20: /* Caps Lock */ return;
2033 case 27: /* Escape */ ch = '\u001B'; break;
2034 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2035 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2036 case 35: /* End */ ch = '\u001BOF'; break;
2037 case 36: /* Home */ ch = '\u001BOH'; break;
2038 case 37: /* Left */ ch = this.cursorKeyMode ?
2039 '\u001BOD' : '\u001B[D'; break;
2040 case 38: /* Up */ ch = this.cursorKeyMode ?
2041 '\u001BOA' : '\u001B[A'; break;
2042 case 39: /* Right */ ch = this.cursorKeyMode ?
2043 '\u001BOC' : '\u001B[C'; break;
2044 case 40: /* Down */ ch = this.cursorKeyMode ?
2045 '\u001BOB' : '\u001B[B'; break;
2046 case 45: /* Insert */ ch = '\u001B[2~'; break;
2047 case 46: /* Delete */ ch = '\u001B[3~'; break;
2048 case 91: /* Left Window */ return;
2049 case 92: /* Right Window */ return;
2050 case 93: /* Select */ return;
2051 case 96: /* 0 */ ch = '0'; break;
2052 case 97: /* 1 */ ch = '1'; break;
2053 case 98: /* 2 */ ch = '2'; break;
2054 case 99: /* 3 */ ch = '3'; break;
2055 case 100: /* 4 */ ch = '4'; break;
2056 case 101: /* 5 */ ch = '5'; break;
2057 case 102: /* 6 */ ch = '6'; break;
2058 case 103: /* 7 */ ch = '7'; break;
2059 case 104: /* 8 */ ch = '8'; break;
2060 case 105: /* 9 */ ch = '9'; break;
2061 case 106: /* * */ ch = '*'; break;
2062 case 107: /* + */ ch = '+'; break;
2063 case 109: /* - */ ch = '-'; break;
2064 case 110: /* . */ ch = '.'; break;
2065 case 111: /* / */ ch = '/'; break;
2066 case 112: /* F1 */ ch = '\u001BOP'; break;
2067 case 113: /* F2 */ ch = '\u001BOQ'; break;
2068 case 114: /* F3 */ ch = '\u001BOR'; break;
2069 case 115: /* F4 */ ch = '\u001BOS'; break;
2070 case 116: /* F5 */ ch = '\u001B[15~'; break;
2071 case 117: /* F6 */ ch = '\u001B[17~'; break;
2072 case 118: /* F7 */ ch = '\u001B[18~'; break;
2073 case 119: /* F8 */ ch = '\u001B[19~'; break;
2074 case 120: /* F9 */ ch = '\u001B[20~'; break;
2075 case 121: /* F10 */ ch = '\u001B[21~'; break;
2076 case 122: /* F11 */ ch = '\u001B[23~'; break;
2077 case 123: /* F12 */ ch = '\u001B[24~'; break;
2078 case 144: /* Num Lock */ return;
2079 case 145: /* Scroll Lock */ return;
2082 this.scrollable.scrollTop = this.numScrollbackLines *
2083 this.cursorHeight + 1;
2087 // "ch" now contains the sequence of keycodes to send. But we might still
2088 // have to apply the effects of modifier keys.
2089 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2090 var start, digit, part1, part2;
2091 if ((start = ch.substr(0, 2)) == '\u001B[') {
2093 part1.length < ch.length &&
2094 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2095 part1 = ch.substr(0, part1.length + 1);
2097 part2 = ch.substr(part1.length);
2098 if (part1.length > 2) {
2101 } else if (start == '\u001BO') {
2103 part2 = ch.substr(2);
2105 if (part1 != undefined) {
2107 ((event.shiftKey ? 1 : 0) +
2108 (event.altKey|event.metaKey ? 2 : 0) +
2109 (event.ctrlKey ? 4 : 0)) +
2111 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2116 if (this.menu.style.visibility == 'hidden') {
2117 // this.vt100('R: c=');
2118 // for (var i = 0; i < ch.length; i++)
2119 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2120 // this.vt100('\r\n');
2121 this.keysPressed(ch);
2125 VT100.prototype.inspect = function(o, d) {
2126 if (d == undefined) {
2130 if (typeof o == 'object' && ++d < 2) {
2133 rc += this.spaces(d * 2) + i + ' -> ';
2135 rc += this.inspect(o[i], d);
2137 rc += '?' + '?' + '?\r\n';
2142 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2147 VT100.prototype.checkComposedKeys = function(event) {
2148 // Composed keys (at least on Linux) do not generate normal events.
2149 // Instead, they get entered into the text field. We normally catch
2150 // this on the next keyup event.
2151 var s = this.input.value;
2153 this.input.value = '';
2154 if (this.menu.style.visibility == 'hidden') {
2155 this.keysPressed(s);
2160 VT100.prototype.fixEvent = function(event) {
2161 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2162 // is used as a second-level selector, clear the modifier bits before
2163 // handling the event.
2164 if (event.ctrlKey && event.altKey) {
2166 fake.charCode = event.charCode;
2167 fake.keyCode = event.keyCode;
2168 fake.ctrlKey = false;
2169 fake.shiftKey = event.shiftKey;
2170 fake.altKey = false;
2171 fake.metaKey = event.metaKey;
2175 // Some browsers fail to translate keys, if both shift and alt/meta is
2176 // pressed at the same time. We try to translate those cases, but that
2177 // only works for US keyboard layouts.
2178 if (event.shiftKey) {
2181 switch (this.lastNormalKeyDownEvent.keyCode) {
2182 case 39: /* ' -> " */ u = 39; s = 34; break;
2183 case 44: /* , -> < */ u = 44; s = 60; break;
2184 case 45: /* - -> _ */ u = 45; s = 95; break;
2185 case 46: /* . -> > */ u = 46; s = 62; break;
2186 case 47: /* / -> ? */ u = 47; s = 63; break;
2188 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2189 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2190 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2191 case 51: /* 3 -> # */ u = 51; s = 35; break;
2192 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2193 case 53: /* 5 -> % */ u = 53; s = 37; break;
2194 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2195 case 55: /* 7 -> & */ u = 55; s = 38; break;
2196 case 56: /* 8 -> * */ u = 56; s = 42; break;
2197 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2199 case 59: /* ; -> : */ u = 59; s = 58; break;
2200 case 61: /* = -> + */ u = 61; s = 43; break;
2201 case 91: /* [ -> { */ u = 91; s = 123; break;
2202 case 92: /* \ -> | */ u = 92; s = 124; break;
2203 case 93: /* ] -> } */ u = 93; s = 125; break;
2204 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2206 case 109: /* - -> _ */ u = 45; s = 95; break;
2207 case 111: /* / -> ? */ u = 47; s = 63; break;
2209 case 186: /* ; -> : */ u = 59; s = 58; break;
2210 case 187: /* = -> + */ u = 61; s = 43; break;
2211 case 188: /* , -> < */ u = 44; s = 60; break;
2212 case 189: /* - -> _ */ u = 45; s = 95; break;
2213 case 190: /* . -> > */ u = 46; s = 62; break;
2214 case 191: /* / -> ? */ u = 47; s = 63; break;
2215 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2216 case 219: /* [ -> { */ u = 91; s = 123; break;
2217 case 220: /* \ -> | */ u = 92; s = 124; break;
2218 case 221: /* ] -> } */ u = 93; s = 125; break;
2219 case 222: /* ' -> " */ u = 39; s = 34; break;
2222 if (s && (event.charCode == u || event.charCode == 0)) {
2225 fake.keyCode = event.keyCode;
2226 fake.ctrlKey = event.ctrlKey;
2227 fake.shiftKey = event.shiftKey;
2228 fake.altKey = event.altKey;
2229 fake.metaKey = event.metaKey;
2236 VT100.prototype.keyDown = function(event) {
2237 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2238 // (event.shiftKey || event.ctrlKey || event.altKey ||
2239 // event.metaKey ? ', ' +
2240 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2241 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2243 this.checkComposedKeys(event);
2244 this.lastKeyPressedEvent = undefined;
2245 this.lastKeyDownEvent = undefined;
2246 this.lastNormalKeyDownEvent = event;
2249 event.keyCode == 32 ||
2250 event.keyCode >= 48 && event.keyCode <= 57 ||
2251 event.keyCode >= 65 && event.keyCode <= 90;
2254 event.keyCode >= 96 && event.keyCode <= 105;
2257 event.keyCode == 59 || event.keyCode == 61 ||
2258 event.keyCode == 106 || event.keyCode == 107 ||
2259 event.keyCode >= 109 && event.keyCode <= 111 ||
2260 event.keyCode >= 186 && event.keyCode <= 192 ||
2261 event.keyCode >= 219 && event.keyCode <= 222 ||
2262 event.keyCode == 226 || event.keyCode == 252;
2264 if (navigator.appName == 'Konqueror') {
2265 normalKey |= event.keyCode < 128;
2270 // We normally prefer to look at keypress events, as they perform the
2271 // translation from keyCode to charCode. This is important, as the
2272 // translation is locale-dependent.
2273 // But for some keys, we must intercept them during the keydown event,
2274 // as they would otherwise get interpreted by the browser.
2275 // Even, when doing all of this, there are some keys that we can never
2276 // intercept. This applies to some of the menu navigation keys in IE.
2277 // In fact, we see them, but we cannot stop IE from seeing them, too.
2278 if ((event.charCode || event.keyCode) &&
2279 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2281 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2282 // interpret this sequence ourselves, as some keyboard layouts use
2283 // it for second-level layouts.
2284 !(event.ctrlKey && event.altKey)) ||
2285 this.catchModifiersEarly && normalKey && !alphNumKey &&
2286 (event.ctrlKey || event.altKey || event.metaKey) ||
2288 this.lastKeyDownEvent = event;
2290 fake.ctrlKey = event.ctrlKey;
2291 fake.shiftKey = event.shiftKey;
2292 fake.altKey = event.altKey;
2293 fake.metaKey = event.metaKey;
2295 fake.charCode = event.keyCode;
2299 fake.keyCode = event.keyCode;
2300 if (!alphNumKey && event.shiftKey) {
2301 fake = this.fixEvent(fake);
2305 this.handleKey(fake);
2306 this.lastNormalKeyDownEvent = undefined;
2309 // For non-IE browsers
2310 event.stopPropagation();
2311 event.preventDefault();
2316 event.cancelBubble = true;
2317 event.returnValue = false;
2327 VT100.prototype.keyPressed = function(event) {
2328 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2329 // (event.shiftKey || event.ctrlKey || event.altKey ||
2330 // event.metaKey ? ', ' +
2331 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2332 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2334 if (this.lastKeyDownEvent) {
2335 // If we already processed the key on keydown, do not process it
2336 // again here. Ideally, the browser should not even have generated a
2337 // keypress event in this case. But that does not appear to always work.
2338 this.lastKeyDownEvent = undefined;
2340 this.handleKey(event.altKey || event.metaKey
2341 ? this.fixEvent(event) : event);
2345 // For non-IE browsers
2346 event.preventDefault();
2352 event.cancelBubble = true;
2353 event.returnValue = false;
2358 this.lastNormalKeyDownEvent = undefined;
2359 this.lastKeyPressedEvent = event;
2363 VT100.prototype.keyUp = function(event) {
2364 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2365 // (event.shiftKey || event.ctrlKey || event.altKey ||
2366 // event.metaKey ? ', ' +
2367 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2368 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2370 if (this.lastKeyPressedEvent) {
2371 // The compose key on Linux occasionally confuses the browser and keeps
2372 // inserting bogus characters into the input field, even if just a regular
2373 // key has been pressed. Detect this case and drop the bogus characters.
2375 event.srcElement).value = '';
2377 // This is usually were we notice that a key has been composed and
2378 // thus failed to generate normal events.
2379 this.checkComposedKeys(event);
2381 // Some browsers don't report keypress events if ctrl or alt is pressed
2382 // for non-alphanumerical keys. Patch things up for now, but in the
2383 // future we will catch these keys earlier (in the keydown handler).
2384 if (this.lastNormalKeyDownEvent) {
2385 this.catchModifiersEarly = true;
2387 event.keyCode == 32 ||
2388 event.keyCode >= 48 && event.keyCode <= 57 ||
2389 event.keyCode >= 65 && event.keyCode <= 90;
2392 event.keyCode >= 96 && event.keyCode <= 105;
2395 event.keyCode == 59 || event.keyCode == 61 ||
2396 event.keyCode == 106 || event.keyCode == 107 ||
2397 event.keyCode >= 109 && event.keyCode <= 111 ||
2398 event.keyCode >= 186 && event.keyCode <= 192 ||
2399 event.keyCode >= 219 && event.keyCode <= 222 ||
2400 event.keyCode == 252;
2402 fake.ctrlKey = event.ctrlKey;
2403 fake.shiftKey = event.shiftKey;
2404 fake.altKey = event.altKey;
2405 fake.metaKey = event.metaKey;
2407 fake.charCode = event.keyCode;
2411 fake.keyCode = event.keyCode;
2412 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2413 fake = this.fixEvent(fake);
2416 this.lastNormalKeyDownEvent = undefined;
2417 this.handleKey(fake);
2423 event.cancelBubble = true;
2424 event.returnValue = false;
2429 this.lastKeyDownEvent = undefined;
2430 this.lastKeyPressedEvent = undefined;
2434 VT100.prototype.animateCursor = function(inactive) {
2435 if (!this.cursorInterval) {
2436 this.cursorInterval = setInterval(
2439 vt100.animateCursor();
2441 // Use this opportunity to check whether the user entered a composed
2442 // key, or whether somebody pasted text into the textfield.
2443 vt100.checkComposedKeys();
2447 if (inactive != undefined || this.cursor.className != 'inactive') {
2449 this.cursor.className = 'inactive';
2451 this.cursor.className = this.cursor.className == 'bright'
2457 VT100.prototype.blurCursor = function() {
2458 this.animateCursor(true);
2461 VT100.prototype.focusCursor = function() {
2462 this.animateCursor(false);
2465 VT100.prototype.flashScreen = function() {
2466 this.isInverted = !this.isInverted;
2467 this.refreshInvertedState();
2468 this.isInverted = !this.isInverted;
2469 setTimeout(function(vt100) {
2471 vt100.refreshInvertedState();
2476 VT100.prototype.beep = function() {
2477 if (this.visualBell) {
2484 this.beeper.src = 'beep.wav';
2491 VT100.prototype.bs = function() {
2492 if (this.cursorX > 0) {
2493 this.gotoXY(this.cursorX - 1, this.cursorY);
2494 this.needWrap = false;
2498 VT100.prototype.ht = function(count) {
2499 if (count == undefined) {
2502 var cx = this.cursorX;
2503 while (count-- > 0) {
2504 while (cx++ < this.terminalWidth) {
2505 var tabState = this.userTabStop[cx];
2506 if (tabState == false) {
2507 // Explicitly cleared tab stop
2509 } else if (tabState) {
2510 // Explicitly set tab stop
2513 // Default tab stop at each eighth column
2520 if (cx > this.terminalWidth - 1) {
2521 cx = this.terminalWidth - 1;
2523 if (cx != this.cursorX) {
2524 this.gotoXY(cx, this.cursorY);
2528 VT100.prototype.rt = function(count) {
2529 if (count == undefined) {
2532 var cx = this.cursorX;
2533 while (count-- > 0) {
2535 var tabState = this.userTabStop[cx];
2536 if (tabState == false) {
2537 // Explicitly cleared tab stop
2539 } else if (tabState) {
2540 // Explicitly set tab stop
2543 // Default tab stop at each eighth column
2553 if (cx != this.cursorX) {
2554 this.gotoXY(cx, this.cursorY);
2558 VT100.prototype.cr = function() {
2559 this.gotoXY(0, this.cursorY);
2560 this.needWrap = false;
2563 VT100.prototype.lf = function(count) {
2564 if (count == undefined) {
2567 if (count > this.terminalHeight) {
2568 count = this.terminalHeight;
2574 while (count-- > 0) {
2575 if (this.cursorY == this.bottom - 1) {
2576 this.scrollRegion(0, this.top + 1,
2577 this.terminalWidth, this.bottom - this.top - 1,
2580 } else if (this.cursorY < this.terminalHeight - 1) {
2581 this.gotoXY(this.cursorX, this.cursorY + 1);
2586 VT100.prototype.ri = function(count) {
2587 if (count == undefined) {
2590 if (count > this.terminalHeight) {
2591 count = this.terminalHeight;
2597 while (count-- > 0) {
2598 if (this.cursorY == this.top) {
2599 this.scrollRegion(0, this.top,
2600 this.terminalWidth, this.bottom - this.top - 1,
2602 } else if (this.cursorY > 0) {
2603 this.gotoXY(this.cursorX, this.cursorY - 1);
2606 this.needWrap = false;
2609 VT100.prototype.respondID = function() {
2610 this.respondString += '\u001B[?6c';
2613 VT100.prototype.respondSecondaryDA = function() {
2614 this.respondString += '\u001B[>0;0;0c';
2617 VT100.prototype.updateStyle = function() {
2619 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2620 style += 'text-decoration:underline;';
2622 var bg = (this.attr >> 4) & 0xF;
2623 var fg = this.attr & 0xF;
2624 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2629 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2630 fg = 8; // Dark grey
2631 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2634 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2637 // Make some readability enhancements. Most notably, disallow identical
2638 // background and foreground colors.
2640 if ((fg ^= 8) == 7) {
2644 // And disallow bright colors on a light-grey background.
2645 if (bg == 7 && fg >= 8) {
2646 if ((fg -= 8) == 7) {
2652 style += 'color:' + this.ansi[fg] + ';';
2655 style += 'background-color:' + this.ansi[bg] + ';';
2657 this.attributeHelper.cssText = style;
2658 this.style = this.attributeHelper.cssText;
2661 VT100.prototype.setAttrColors = function(attr) {
2662 if (attr != this.attr) {
2668 VT100.prototype.saveCursor = function() {
2669 this.savedX[this.currentScreen] = this.cursorX;
2670 this.savedY[this.currentScreen] = this.cursorY;
2671 this.savedAttr[this.currentScreen] = this.attr;
2672 this.savedUseGMap = this.useGMap;
2673 for (var i = 0; i < 4; i++) {
2674 this.savedGMap[i] = this.GMap[i];
2676 this.savedValid[this.currentScreen] = true;
2679 VT100.prototype.restoreCursor = function() {
2680 if (!this.savedValid[this.currentScreen]) {
2683 this.attr = this.savedAttr[this.currentScreen];
2685 this.useGMap = this.savedUseGMap;
2686 for (var i = 0; i < 4; i++) {
2687 this.GMap[i] = this.savedGMap[i];
2689 this.translate = this.GMap[this.useGMap];
2690 this.needWrap = false;
2691 this.gotoXY(this.savedX[this.currentScreen],
2692 this.savedY[this.currentScreen]);
2695 VT100.prototype.setMode = function(state) {
2696 for (var i = 0; i <= this.npar; i++) {
2697 if (this.isQuestionMark) {
2698 switch (this.par[i]) {
2699 case 1: this.cursorKeyMode = state; break;
2700 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2701 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2702 case 6: this.offsetMode = state; break;
2703 case 7: this.autoWrapMode = state; break;
2705 case 9: this.mouseReporting = state; break;
2706 case 25: this.cursorNeedsShowing = state;
2707 if (state) { this.showCursor(); }
2708 else { this.hideCursor(); } break;
2711 case 47: this.enableAlternateScreen(state); break;
2715 switch (this.par[i]) {
2716 case 3: this.dispCtrl = state; break;
2717 case 4: this.insertMode = state; break;
2718 case 20:this.crLfMode = state; break;
2725 VT100.prototype.statusReport = function() {
2726 // Ready and operational.
2727 this.respondString += '\u001B[0n';
2730 VT100.prototype.cursorReport = function() {
2731 this.respondString += '\u001B[' +
2732 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2734 (this.cursorX + 1) +
2738 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2739 // Changing of cursor color is not implemented.
2742 VT100.prototype.csiAt = function(number) {
2747 if (number > this.terminalWidth - this.cursorX) {
2748 number = this.terminalWidth - this.cursorX;
2750 this.scrollRegion(this.cursorX, this.cursorY,
2751 this.terminalWidth - this.cursorX - number, 1,
2752 number, 0, this.style);
2753 this.needWrap = false;
2756 VT100.prototype.csiJ = function(number) {
2758 case 0: // Erase from cursor to end of display
2759 this.clearRegion(this.cursorX, this.cursorY,
2760 this.terminalWidth - this.cursorX, 1, this.style);
2761 if (this.cursorY < this.terminalHeight-2) {
2762 this.clearRegion(0, this.cursorY+1,
2763 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2767 case 1: // Erase from start to cursor
2768 if (this.cursorY > 0) {
2769 this.clearRegion(0, 0,
2770 this.terminalWidth, this.cursorY, this.style);
2772 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2774 case 2: // Erase whole display
2775 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
2783 VT100.prototype.csiK = function(number) {
2785 case 0: // Erase from cursor to end of line
2786 this.clearRegion(this.cursorX, this.cursorY,
2787 this.terminalWidth - this.cursorX, 1, this.style);
2789 case 1: // Erase from start of line to cursor
2790 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2792 case 2: // Erase whole line
2793 this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
2801 VT100.prototype.csiL = function(number) {
2802 // Open line by inserting blank line(s)
2803 if (this.cursorY >= this.bottom) {
2809 if (number > this.bottom - this.cursorY) {
2810 number = this.bottom - this.cursorY;
2812 this.scrollRegion(0, this.cursorY,
2813 this.terminalWidth, this.bottom - this.cursorY - number,
2814 0, number, this.style);
2818 VT100.prototype.csiM = function(number) {
2819 // Delete line(s), scrolling up the bottom of the screen.
2820 if (this.cursorY >= this.bottom) {
2826 if (number > this.bottom - this.cursorY) {
2827 number = bottom - cursorY;
2829 this.scrollRegion(0, this.cursorY + number,
2830 this.terminalWidth, this.bottom - this.cursorY - number,
2831 0, -number, this.style);
2835 VT100.prototype.csim = function() {
2836 for (var i = 0; i <= this.npar; i++) {
2837 switch (this.par[i]) {
2838 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2839 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2840 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2841 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2842 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2843 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
2845 this.translate = this.GMap[this.useGMap];
2846 this.dispCtrl = false;
2847 this.toggleMeta = false;
2850 this.translate = this.CodePage437Map;
2851 this.dispCtrl = true;
2852 this.toggleMeta = false;
2855 this.translate = this.CodePage437Map;
2856 this.dispCtrl = true;
2857 this.toggleMeta = true;
2860 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2861 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2862 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2863 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2864 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2865 0x0200 /* ATTR_UNDERLINE */; break;
2866 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
2867 case 49: this.attr |= 0xF0; break;
2869 if (this.par[i] >= 30 && this.par[i] <= 37) {
2870 var fg = this.par[i] - 30;
2871 this.attr = (this.attr & ~0x0F) | fg;
2872 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2873 var bg = this.par[i] - 40;
2874 this.attr = (this.attr & ~0xF0) | (bg << 4);
2882 VT100.prototype.csiP = function(number) {
2883 // Delete character(s) following cursor
2887 if (number > this.terminalWidth - this.cursorX) {
2888 number = this.terminalWidth - this.cursorX;
2890 this.scrollRegion(this.cursorX + number, this.cursorY,
2891 this.terminalWidth - this.cursorX - number, 1,
2892 -number, 0, this.style);
2896 VT100.prototype.csiX = function(number) {
2897 // Clear characters following cursor
2901 if (number > this.terminalWidth - this.cursorX) {
2902 number = this.terminalWidth - this.cursorX;
2904 this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
2908 VT100.prototype.settermCommand = function() {
2909 // Setterm commands are not implemented
2912 VT100.prototype.doControl = function(ch) {
2915 case 0x00: /* ignored */ break;
2916 case 0x08: this.bs(); break;
2917 case 0x09: this.ht(); break;
2921 case 0x84: this.lf(); if (!this.crLfMode) break;
2922 case 0x0D: this.cr(); break;
2923 case 0x85: this.cr(); this.lf(); break;
2924 case 0x0E: this.useGMap = 1;
2925 this.translate = this.GMap[1];
2926 this.dispCtrl = true; break;
2927 case 0x0F: this.useGMap = 0;
2928 this.translate = this.GMap[0];
2929 this.dispCtrl = false; break;
2931 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
2932 case 0x1B: this.isEsc = 1 /* ESesc */; break;
2933 case 0x7F: /* ignored */ break;
2934 case 0x88: this.userTabStop[this.cursorX] = true; break;
2935 case 0x8D: this.ri(); break;
2936 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
2937 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
2938 case 0x9A: this.respondID(); break;
2939 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
2940 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
2944 default: switch (this.isEsc) {
2946 this.isEsc = 0 /* ESnormal */;
2948 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
2949 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
2951 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
2953 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
2955 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
2956 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
2957 /*7*/ case 0x37: this.saveCursor(); break;
2958 /*8*/ case 0x38: this.restoreCursor(); break;
2959 /*>*/ case 0x3E: this.applKeyMode = false; break;
2960 /*=*/ case 0x3D: this.applKeyMode = true; break;
2961 /*D*/ case 0x44: this.lf(); break;
2962 /*E*/ case 0x45: this.cr(); this.lf(); break;
2963 /*M*/ case 0x4D: this.ri(); break;
2964 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
2965 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
2966 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
2967 /*Z*/ case 0x5A: this.respondID(); break;
2968 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
2969 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
2970 /*c*/ case 0x63: this.reset(); break;
2971 /*g*/ case 0x67: this.flashScreen(); break;
2975 case 15 /* ESnonstd */:
2979 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
2980 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
2981 this.isEsc = 16 /* ESpalette */; break;
2982 /*R*/ case 0x52: // Palette support is not implemented
2983 this.isEsc = 0 /* ESnormal */; break;
2984 default: this.isEsc = 0 /* ESnormal */; break;
2987 case 16 /* ESpalette */:
2988 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
2989 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
2990 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
2991 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
2993 if (this.npar == 7) {
2994 // Palette support is not implemented
2995 this.isEsc = 0 /* ESnormal */;
2998 this.isEsc = 0 /* ESnormal */;
3001 case 2 /* ESsquare */:
3003 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3004 0, 0, 0, 0, 0, 0, 0, 0 ];
3005 this.isEsc = 3 /* ESgetpars */;
3006 /*[*/ if (ch == 0x5B) { // Function key
3007 this.isEsc = 6 /* ESfunckey */;
3010 /*?*/ this.isQuestionMark = ch == 0x3F;
3011 if (this.isQuestionMark) {
3016 case 5 /* ESdeviceattr */:
3017 case 3 /* ESgetpars */:
3018 /*;*/ if (ch == 0x3B) {
3021 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3022 var par = this.par[this.npar];
3023 if (par == undefined) {
3026 this.par[this.npar] = 10*par + (ch & 0xF);
3028 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3030 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3031 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3032 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3033 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3036 this.isEsc = 0 /* ESnormal */;
3039 this.isEsc = 4 /* ESgotpars */;
3042 case 4 /* ESgotpars */:
3043 this.isEsc = 0 /* ESnormal */;
3044 if (this.isQuestionMark) {
3046 /*h*/ case 0x68: this.setMode(true); break;
3047 /*l*/ case 0x6C: this.setMode(false); break;
3048 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3051 this.isQuestionMark = false;
3055 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3056 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3058 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3059 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3060 this.cursorY - (this.par[0] ? this.par[0] : 1));
3063 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3064 this.cursorY + (this.par[0] ? this.par[0] : 1));
3067 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3068 this.cursorY); break;
3069 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3070 this.cursorY); break;
3071 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3073 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3075 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3077 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3078 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3079 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3080 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3081 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3082 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3083 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3084 /*m*/ case 0x6D: this.csim(); break;
3085 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3086 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3087 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3088 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3089 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3090 /*g*/ case 0x67: if (this.par[0] == 0) {
3091 this.userTabStop[this.cursorX] = false;
3092 } else if (this.par[0] == 2 || this.par[0] == 3) {
3093 this.userTabStop = [ ];
3094 for (var i = 0; i < this.terminalWidth; i++) {
3095 this.userTabStop[i] = false;
3099 /*h*/ case 0x68: this.setMode(true); break;
3100 /*l*/ case 0x6C: this.setMode(false); break;
3101 /*n*/ case 0x6E: switch (this.par[0]) {
3102 case 5: this.statusReport(); break;
3103 case 6: this.cursorReport(); break;
3107 /*q*/ case 0x71: // LED control not implemented
3109 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3110 var b = this.par[1] ? this.par[1]
3111 : this.terminalHeight;
3112 if (t < b && b <= this.terminalHeight) {
3118 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3119 if (c > this.terminalWidth * this.terminalHeight) {
3120 c = this.terminalWidth * this.terminalHeight;
3123 lineBuf += this.lastCharacter;
3126 /*s*/ case 0x73: this.saveCursor(); break;
3127 /*u*/ case 0x75: this.restoreCursor(); break;
3128 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3129 /*]*/ case 0x5D: this.settermCommand(); break;
3133 case 12 /* ESbang */:
3137 this.isEsc = 0 /* ESnormal */;
3139 case 13 /* ESpercent */:
3140 this.isEsc = 0 /* ESnormal */;
3142 /*@*/ case 0x40: this.utfEnabled = false; break;
3144 /*8*/ case 0x38: this.utfEnabled = true; break;
3148 case 6 /* ESfunckey */:
3149 this.isEsc = 0 /* ESnormal */; break;
3150 case 7 /* EShash */:
3151 this.isEsc = 0 /* ESnormal */;
3152 /*8*/ if (ch == 0x38) {
3153 // Screen alignment test not implemented
3156 case 8 /* ESsetG0 */:
3157 case 9 /* ESsetG1 */:
3158 case 10 /* ESsetG2 */:
3159 case 11 /* ESsetG3 */:
3160 var g = this.isEsc - 8 /* ESsetG0 */;
3161 this.isEsc = 0 /* ESnormal */;
3163 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3165 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3166 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3167 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3170 if (this.useGMap == g) {
3171 this.translate = this.GMap[g];
3174 case 17 /* ESstatus */:
3176 if (this.statusString && this.statusString.charAt(0) == ';') {
3177 this.statusString = this.statusString.substr(1);
3180 window.status = this.statusString;
3183 this.isEsc = 0 /* ESnormal */;
3185 this.statusString += String.fromCharCode(ch);
3188 case 18 /* ESss2 */:
3189 case 19 /* ESss3 */:
3191 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3192 [this.toggleMeta ? (ch | 0x80) : ch];
3193 if ((ch & 0xFF00) == 0xF000) {
3195 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3196 this.isEsc = 0 /* ESnormal */; break;
3199 this.lastCharacter = String.fromCharCode(ch);
3200 lineBuf += this.lastCharacter;
3201 this.isEsc = 0 /* ESnormal */; break;
3203 this.isEsc = 0 /* ESnormal */; break;
3210 VT100.prototype.renderString = function(s, showCursor) {
3211 // We try to minimize the number of DOM operations by coalescing individual
3212 // characters into strings. This is a significant performance improvement.
3213 var incX = s.length;
3214 if (incX > this.terminalWidth - this.cursorX) {
3215 incX = this.terminalWidth - this.cursorX;
3219 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3222 // Minimize the number of calls to putString(), by avoiding a direct
3223 // call to this.showCursor()
3224 this.cursor.style.visibility = '';
3226 this.putString(this.cursorX, this.cursorY, s, this.style);
3229 VT100.prototype.vt100 = function(s) {
3230 this.cursorNeedsShowing = this.hideCursor();
3231 this.respondString = '';
3233 for (var i = 0; i < s.length; i++) {
3234 var ch = s.charCodeAt(i);
3235 if (this.utfEnabled) {
3236 // Decode UTF8 encoded character
3238 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3239 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3240 if (--this.utfCount <= 0) {
3241 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3250 if ((ch & 0xE0) == 0xC0) {
3252 this.utfChar = ch & 0x1F;
3253 } else if ((ch & 0xF0) == 0xE0) {
3255 this.utfChar = ch & 0x0F;
3256 } else if ((ch & 0xF8) == 0xF0) {
3258 this.utfChar = ch & 0x07;
3259 } else if ((ch & 0xFC) == 0xF8) {
3261 this.utfChar = ch & 0x03;
3262 } else if ((ch & 0xFE) == 0xFC) {
3264 this.utfChar = ch & 0x01;
3274 var isNormalCharacter =
3275 (ch >= 32 && ch <= 127 || ch >= 160 ||
3276 this.utfEnabled && ch >= 128 ||
3277 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3278 (ch != 0x7F || this.dispCtrl);
3280 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3282 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3284 if ((ch & 0xFF00) == 0xF000) {
3286 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3289 if (this.needWrap || this.insertMode) {
3291 this.renderString(lineBuf);
3295 if (this.needWrap) {
3296 this.cr(); this.lf();
3298 if (this.insertMode) {
3299 this.scrollRegion(this.cursorX, this.cursorY,
3300 this.terminalWidth - this.cursorX - 1, 1,
3303 this.lastCharacter = String.fromCharCode(ch);
3304 lineBuf += this.lastCharacter;
3305 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3306 this.needWrap = this.autoWrapMode;
3310 this.renderString(lineBuf);
3313 var expand = this.doControl(ch);
3314 if (expand.length) {
3315 var r = this.respondString;
3316 this.respondString= r + this.vt100(expand);
3321 this.renderString(lineBuf, this.cursorNeedsShowing);
3322 } else if (this.cursorNeedsShowing) {
3325 return this.respondString;
3328 VT100.prototype.Latin1Map = [
3329 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3330 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3331 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3332 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3333 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3334 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3335 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3336 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3337 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3338 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3339 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3340 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3341 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3342 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3343 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3344 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3345 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3346 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3347 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3348 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3349 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3350 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3351 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3352 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3353 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3354 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3355 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3356 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3357 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3358 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3359 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3360 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3363 VT100.prototype.VT100GraphicsMap = [
3364 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3365 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3366 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3367 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3368 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3369 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3370 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3371 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3372 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3373 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3374 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3375 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3376 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3377 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3378 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3379 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3380 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3381 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3382 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3383 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3384 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3385 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3386 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3387 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3388 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3389 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3390 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3391 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3392 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3393 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3394 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3395 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3398 VT100.prototype.CodePage437Map = [
3399 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3400 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3401 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3402 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3403 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3404 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3405 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3406 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3407 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3408 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3409 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3410 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3411 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3412 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3413 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3414 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3415 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3416 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3417 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3418 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3419 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3420 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3421 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3422 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3423 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3424 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3425 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3426 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3427 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3428 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3429 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3430 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3433 VT100.prototype.DirectToFontMap = [
3434 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3435 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3436 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3437 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3438 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3439 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3440 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3441 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3442 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3443 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3444 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3445 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3446 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3447 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3448 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3449 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3450 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3451 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3452 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3453 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3454 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3455 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3456 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3457 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3458 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3459 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3460 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3461 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3462 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3463 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3464 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3465 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3468 VT100.prototype.ctrlAction = [
3469 true, false, false, false, false, false, false, true,
3470 true, true, true, true, true, true, true, true,
3471 false, false, false, false, false, false, false, false,
3472 true, false, true, true, false, false, false, false
3475 VT100.prototype.ctrlAlways = [
3476 true, false, false, false, false, false, false, false,
3477 true, false, true, false, true, true, true, true,
3478 false, false, false, false, false, false, false, false,
3479 false, false, false, true, false, false, false, false