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.maxScrollbackLines = 500;
180 this.isQuestionMark = false;
183 this.savedAttr = [ ];
184 this.savedUseGMap = 0;
185 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
186 this.CodePage437Map, this.DirectToFontMap ];
187 this.savedValid = [ ];
188 this.respondString = '';
189 this.statusString = '';
190 this.internalClipboard = undefined;
194 VT100.prototype.reset = function(clearHistory) {
195 this.isEsc = 0 /* ESnormal */;
196 this.needWrap = false;
197 this.autoWrapMode = true;
198 this.dispCtrl = false;
199 this.toggleMeta = false;
200 this.insertMode = false;
201 this.applKeyMode = false;
202 this.cursorKeyMode = false;
203 this.crLfMode = false;
204 this.offsetMode = false;
205 this.mouseReporting = false;
206 this.utfEnabled = true;
207 this.visualBell = typeof suppressAllAudio !=
212 this.color = 'ansi0 bgAnsi15';
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,
240 this.color, this.style);
243 VT100.prototype.addListener = function(elem, event, listener) {
244 if (elem.addEventListener) {
245 elem.addEventListener(event, listener, false);
247 elem.attachEvent('on' + event, listener);
251 VT100.prototype.initializeUserCSSStyles = function() {
252 this.usercssActions = [];
253 if (typeof userCSSList != 'undefined') {
256 var wasSingleSel = 1;
257 var beginOfGroup = 0;
258 for (var i = 0; i <= userCSSList.length; ++i) {
259 if (i < userCSSList.length) {
260 var label = userCSSList[i][0];
261 var newGroup = userCSSList[i][1];
262 var enabled = userCSSList[i][2];
264 // Add user style sheet to document
265 var style = document.createElement('link');
266 var id = document.createAttribute('id');
267 id.nodeValue = 'usercss-' + i;
268 style.setAttributeNode(id);
269 var rel = document.createAttribute('rel');
270 rel.nodeValue = 'stylesheet';
271 style.setAttributeNode(rel);
272 var href = document.createAttribute('href');
273 href.nodeValue = 'usercss-' + i + '.css';
274 style.setAttributeNode(href);
275 var type = document.createAttribute('type');
276 type.nodeValue = 'text/css';
277 style.setAttributeNode(type);
278 document.getElementsByTagName('head')[0].appendChild(style);
279 style.disabled = !enabled;
283 if (newGroup || i == userCSSList.length) {
284 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
285 // The last group had multiple entries that are mutually exclusive;
286 // or the previous to last group did. In either case, we need to
287 // append a "<hr />" before we can add the last group to the menu.
290 wasSingleSel = i - beginOfGroup < 1;
294 for (var j = beginOfGroup; j < i; ++j) {
295 this.usercssActions[this.usercssActions.length] =
296 function(vt100, current, begin, count) {
298 // Deselect all other entries in the group, then either select
299 // (for multiple entries in group) or toggle (for on/off entry)
300 // the current entry.
302 var entry = vt100.getChildById(vt100.menu,
306 for (var c = count; c > 0; ++j) {
307 if (entry.tagName == 'LI') {
310 var label = vt100.usercss.childNodes[j];
312 // Restore label to just the text content
313 if (typeof label.textContent == 'undefined') {
314 var s = label.innerText;
315 label.innerHTML = '';
316 label.appendChild(document.createTextNode(s));
318 label.textContent= label.textContent;
321 // User style sheets are number sequentially
322 var sheet = document.getElementById(
326 sheet.disabled = !sheet.disabled;
328 sheet.disabled = false;
330 if (!sheet.disabled) {
331 label.innerHTML= '<img src="enabled.gif" />' +
335 sheet.disabled = true;
339 entry = entry.nextSibling;
342 }(this, j, beginOfGroup, i - beginOfGroup);
345 if (i == userCSSList.length) {
351 // Collect all entries in a group, before attaching them to the menu.
352 // This is necessary as we don't know whether this is a group of
353 // mutually exclusive options (which should be separated by "<hr />" on
354 // both ends), or whether this is a on/off toggle, which can be grouped
355 // together with other on/off options.
357 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
361 this.usercss.innerHTML = menu;
365 VT100.prototype.initializeElements = function(container) {
366 // If the necessary objects have not already been defined in the HTML
367 // page, create them now.
369 this.container = container;
370 } else if (!(this.container = document.getElementById('vt100'))) {
371 this.container = document.createElement('div');
372 this.container.id = 'vt100';
373 document.body.appendChild(this.container);
376 if (!this.getChildById(this.container, 'reconnect') ||
377 !this.getChildById(this.container, 'menu') ||
378 !this.getChildById(this.container, 'scrollable') ||
379 !this.getChildById(this.container, 'console') ||
380 !this.getChildById(this.container, 'alt_console') ||
381 !this.getChildById(this.container, 'ieprobe') ||
382 !this.getChildById(this.container, 'padding') ||
383 !this.getChildById(this.container, 'cursor') ||
384 !this.getChildById(this.container, 'lineheight') ||
385 !this.getChildById(this.container, 'usercss') ||
386 !this.getChildById(this.container, 'space') ||
387 !this.getChildById(this.container, 'input') ||
388 !this.getChildById(this.container, 'cliphelper')) {
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 (typeof suppressAllAudio != 'undefined' &&
436 suppressAllAudio ? "" :
437 embed + '<bgsound id="beep_bgsound" loop=1 />') +
441 // Find the object used for playing the "beep" sound, if any.
442 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
443 this.beeper = undefined;
445 this.beeper = this.getChildById(this.container,
447 if (!this.beeper || !this.beeper.Play) {
448 this.beeper = this.getChildById(this.container,
450 if (!this.beeper || typeof this.beeper.src == 'undefined') {
451 this.beeper = undefined;
456 // Initialize the variables for finding the text console and the
458 this.reconnectBtn = this.getChildById(this.container,'reconnect');
459 this.curSizeBox = this.getChildById(this.container, 'cursize');
460 this.menu = this.getChildById(this.container, 'menu');
461 this.scrollable = this.getChildById(this.container,
463 this.lineheight = this.getChildById(this.container,
466 [ this.getChildById(this.container, 'console'),
467 this.getChildById(this.container, 'alt_console') ];
468 var ieProbe = this.getChildById(this.container, 'ieprobe');
469 this.padding = this.getChildById(this.container, 'padding');
470 this.cursor = this.getChildById(this.container, 'cursor');
471 this.usercss = this.getChildById(this.container, 'usercss');
472 this.space = this.getChildById(this.container, 'space');
473 this.input = this.getChildById(this.container, 'input');
474 this.cliphelper = this.getChildById(this.container,
477 // Add any user selectable style sheets to the menu
478 this.initializeUserCSSStyles();
480 // Remember the dimensions of a standard character glyph. We would
481 // expect that we could just check cursor.clientWidth/Height at any time,
482 // but it turns out that browsers sometimes invalidate these values
483 // (e.g. while displaying a print preview screen).
484 this.cursorWidth = this.cursor.clientWidth;
485 this.cursorHeight = this.lineheight.clientHeight;
487 // IE has a slightly different boxing model, that we need to compensate for
488 this.isIE = ieProbe.offsetTop > 1;
490 this.console.innerHTML = '';
492 // Determine if the terminal window is positioned at the beginning of the
493 // page, or if it is embedded somewhere else in the page. For full-screen
494 // terminals, automatically resize whenever the browser window changes.
495 var marginTop = parseInt(this.getCurrentComputedStyle(
496 document.body, 'marginTop'));
497 var marginLeft = parseInt(this.getCurrentComputedStyle(
498 document.body, 'marginLeft'));
499 var marginRight = parseInt(this.getCurrentComputedStyle(
500 document.body, 'marginRight'));
501 var x = this.container.offsetLeft;
502 var y = this.container.offsetTop;
503 for (var parent = this.container; parent = parent.offsetParent; ) {
504 x += parent.offsetLeft;
505 y += parent.offsetTop;
507 this.isEmbedded = marginTop != y ||
509 (window.innerWidth ||
510 document.documentElement.clientWidth ||
511 document.body.clientWidth) -
512 marginRight != x + this.container.offsetWidth;
513 if (!this.isEmbedded) {
514 // Some browsers generate resize events when the terminal is first
515 // shown. Disable showing the size indicator until a little bit after
516 // the terminal has been rendered the first time.
517 this.indicateSize = false;
518 setTimeout(function(vt100) {
520 vt100.indicateSize = true;
523 this.addListener(window, 'resize',
526 vt100.hideContextMenu();
528 vt100.showCurrentSize();
532 // Hide extra scrollbars attached to window
533 document.body.style.margin = '0px';
534 try { document.body.style.overflow ='hidden'; } catch (e) { }
535 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
539 this.hideContextMenu();
541 // Add listener to reconnect button
542 this.addListener(this.reconnectBtn.firstChild, 'click',
545 var rc = vt100.reconnect();
551 // Add input listeners
552 this.addListener(this.input, 'blur',
554 return function() { vt100.blurCursor(); } }(this));
555 this.addListener(this.input, 'focus',
557 return function() { vt100.focusCursor(); } }(this));
558 this.addListener(this.input, 'keydown',
561 if (!e) e = window.event;
562 return vt100.keyDown(e); } }(this));
563 this.addListener(this.input, 'keypress',
566 if (!e) e = window.event;
567 return vt100.keyPressed(e); } }(this));
568 this.addListener(this.input, 'keyup',
571 if (!e) e = window.event;
572 return vt100.keyUp(e); } }(this));
574 // Attach listeners that move the focus to the <input> field. This way we
575 // can make sure that we can receive keyboard input.
576 var mouseEvent = function(vt100, type) {
578 if (!e) e = window.event;
579 return vt100.mouseEvent(e, type);
582 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
583 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
584 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
586 // Initialize the blank terminal window.
587 this.currentScreen = 0;
590 this.numScrollbackLines = 0;
592 this.bottom = 0x7FFFFFFF;
598 VT100.prototype.getChildById = function(parent, id) {
599 var nodeList = parent.all || parent.getElementsByTagName('*');
600 if (typeof nodeList.namedItem == 'undefined') {
601 for (var i = 0; i < nodeList.length; i++) {
602 if (nodeList[i].id == id) {
608 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
609 return elem ? elem[0] || elem : null;
613 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
614 if (typeof elem.currentStyle != 'undefined') {
615 return elem.currentStyle[style];
617 return document.defaultView.getComputedStyle(elem, null)[style];
621 VT100.prototype.reconnect = function() {
625 VT100.prototype.showReconnect = function(state) {
627 this.reconnectBtn.style.visibility = '';
629 this.reconnectBtn.style.visibility = 'hidden';
633 VT100.prototype.repairElements = function(console) {
634 for (var line = console.firstChild; line; line = line.nextSibling) {
635 if (!line.clientHeight) {
636 var newLine = document.createElement(line.tagName);
637 newLine.style.cssText = line.style.cssText;
638 newLine.className = line.className;
639 if (line.tagName == 'DIV') {
640 for (var span = line.firstChild; span; span = span.nextSibling) {
641 var newSpan = document.createElement(span.tagName);
642 newSpan.style.cssText = span.style.cssText;
643 newSpan.style.className = span.style.className;
644 this.setTextContent(newSpan, this.getTextContent(span));
645 newLine.appendChild(newSpan);
648 this.setTextContent(newLine, this.getTextContent(line));
650 line.parentNode.replaceChild(newLine, line);
656 VT100.prototype.resized = function(w, h) {
659 VT100.prototype.resizer = function() {
660 // The cursor can get corrupted if the print-preview is displayed in Firefox.
661 // Recreating it, will repair it.
662 var newCursor = document.createElement('pre');
663 this.setTextContent(newCursor, ' ');
664 newCursor.id = 'cursor';
665 newCursor.style.cssText = this.cursor.style.cssText;
666 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
667 if (!newCursor.clientHeight) {
668 // Things are broken right now. This is probably because we are
669 // displaying the print-preview. Just don't change any of our settings
670 // until the print dialog is closed again.
671 newCursor.parentNode.removeChild(newCursor);
674 // Swap the old broken cursor for the newly created one.
675 this.cursor.parentNode.removeChild(this.cursor);
676 this.cursor = newCursor;
679 // Really horrible things happen if the contents of the terminal changes
680 // while the print-preview is showing. We get HTML elements that show up
681 // in the DOM, but that do not take up any space. Find these elements and
683 this.repairElements(this.console[0]);
684 this.repairElements(this.console[1]);
686 // Lock the cursor size to the size of a normal character. This helps with
687 // characters that are taller/shorter than normal. Unfortunately, we will
688 // still get confused if somebody enters a character that is wider/narrower
689 // than normal. This can happen if the browser tries to substitute a
690 // characters from a different font.
691 this.cursor.style.width = this.cursorWidth + 'px';
692 this.cursor.style.height = this.cursorHeight + 'px';
694 // Adjust height for one pixel padding of the #vt100 element.
695 // The latter is necessary to properly display the inactive cursor.
696 var console = this.console[this.currentScreen];
697 var height = (this.isEmbedded ? this.container.clientHeight
698 : (window.innerHeight ||
699 document.documentElement.clientHeight ||
700 document.body.clientHeight))-1;
701 var partial = height % this.cursorHeight;
702 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
703 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
704 var oldTerminalHeight = this.terminalHeight;
708 // Clip the cursor to the visible screen.
709 var cx = this.cursorX;
710 var cy = this.cursorY + this.numScrollbackLines;
712 // The alternate screen never keeps a scroll back buffer.
713 this.updateNumScrollbackLines();
714 while (this.currentScreen && this.numScrollbackLines > 0) {
715 console.removeChild(console.firstChild);
716 this.numScrollbackLines--;
718 cy -= this.numScrollbackLines;
721 } else if (cx > this.terminalWidth) {
722 cx = this.terminalWidth - 1;
729 } else if (cy > this.terminalHeight) {
730 cy = this.terminalHeight - 1;
736 // Clip the scroll region to the visible screen.
737 if (this.bottom > this.terminalHeight ||
738 this.bottom == oldTerminalHeight) {
739 this.bottom = this.terminalHeight;
741 if (this.top >= this.bottom) {
742 this.top = this.bottom-1;
748 // Truncate lines, if necessary. Explicitly reposition cursor (this is
749 // particularly important after changing the screen number), and reset
750 // the scroll region to the default.
751 this.truncateLines(this.terminalWidth);
752 this.putString(cx, cy, '', undefined);
753 this.scrollable.scrollTop = this.numScrollbackLines *
754 this.cursorHeight + 1;
756 // Update classNames for lines in the scrollback buffer
757 var line = console.firstChild;
758 for (var i = 0; i < this.numScrollbackLines; i++) {
759 line.className = 'scrollback';
760 line = line.nextSibling;
764 line = line.nextSibling;
767 // Reposition the reconnect button
768 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
769 this.reconnectBtn.clientWidth)/2 + 'px';
770 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
771 this.reconnectBtn.clientHeight)/2 + 'px';
773 // Send notification that the window size has been changed
774 this.resized(this.terminalWidth, this.terminalHeight);
777 VT100.prototype.showCurrentSize = function() {
778 if (!this.indicateSize) {
781 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
783 this.curSizeBox.style.left =
784 (this.terminalWidth*this.cursorWidth -
785 this.curSizeBox.clientWidth)/2 + 'px';
786 this.curSizeBox.style.top =
787 (this.terminalHeight*this.cursorHeight -
788 this.curSizeBox.clientHeight)/2 + 'px';
789 this.curSizeBox.style.visibility = '';
790 if (this.curSizeTimeout) {
791 clearTimeout(this.curSizeTimeout);
794 // Only show the terminal size for a short amount of time after resizing.
795 // Then hide this information, again. Some browsers generate resize events
796 // throughout the entire resize operation. This is nice, and we will show
797 // the terminal size while the user is dragging the window borders.
798 // Other browsers only generate a single event when the user releases the
799 // mouse. In those cases, we can only show the terminal size once at the
800 // end of the resize operation.
801 this.curSizeTimeout = setTimeout(function(vt100) {
803 vt100.curSizeTimeout = null;
804 vt100.curSizeBox.style.visibility = 'hidden';
809 VT100.prototype.selection = function() {
811 return '' + (window.getSelection && window.getSelection() ||
812 document.selection && document.selection.type == 'Text' &&
813 document.selection.createRange().text || '');
819 VT100.prototype.cancelEvent = function(event) {
821 // For non-IE browsers
822 event.stopPropagation();
823 event.preventDefault();
828 event.cancelBubble = true;
829 event.returnValue = false;
837 VT100.prototype.mouseEvent = function(event, type) {
838 // If any text is currently selected, do not move the focus as that would
839 // invalidate the selection.
840 var selection = this.selection();
841 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
845 // Compute mouse position in characters.
846 var offsetX = this.container.offsetLeft;
847 var offsetY = this.container.offsetTop;
848 for (var e = this.container; e = e.offsetParent; ) {
849 offsetX += e.offsetLeft;
850 offsetY += e.offsetTop;
852 var x = (event.clientX - offsetX) / this.cursorWidth;
853 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
854 this.cursorHeight - this.numScrollbackLines;
856 if (x >= this.terminalWidth) {
857 x = this.terminalWidth - 1;
864 if (y >= this.terminalHeight) {
865 y = this.terminalHeight - 1;
873 // Compute button number and modifier keys.
874 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
875 typeof event.pageX != 'undefined' ? event.button :
876 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
877 if (button != undefined) {
878 if (event.shiftKey) {
881 if (event.altKey || event.metaKey) {
889 // Report mouse events if they happen inside of the current screen and
890 // with the SHIFT key unpressed. Both of these restrictions do not apply
891 // for button releases, as we always want to report those.
892 if (this.mouseReporting && !selection.length &&
893 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
894 if (inside || type != 0 /* MOUSE_DOWN */) {
895 if (button != undefined) {
896 var report = '\u001B[M' + String.fromCharCode(button + 32) +
897 String.fromCharCode(x + 33) +
898 String.fromCharCode(y + 33);
899 if (type != 2 /* MOUSE_CLICK */) {
900 this.keysPressed(report);
903 // If we reported the event, stop propagating it (not sure, if this
904 // actually works on most browsers; blocking the global "oncontextmenu"
905 // even is still necessary).
906 return this.cancelEvent(event);
911 // Bring up context menu.
912 if (button == 2 && !event.shiftKey) {
913 if (type == 0 /* MOUSE_DOWN */) {
914 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
916 return this.cancelEvent(event);
919 if (this.mouseReporting) {
921 event.shiftKey = false;
929 VT100.prototype.replaceChar = function(s, ch, repl) {
931 i = s.indexOf(ch, i + 1);
935 s = s.substr(0, i) + repl + s.substr(i + 1);
940 VT100.prototype.htmlEscape = function(s) {
941 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
942 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
945 VT100.prototype.getTextContent = function(elem) {
946 return elem.textContent ||
947 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
950 VT100.prototype.setTextContent = function(elem, s) {
951 // Check if we find any URLs in the text. If so, automatically convert them
953 if (this.urlRE && this.urlRE.test(s)) {
957 if (RegExp.leftContext != null) {
958 inner += this.htmlEscape(RegExp.leftContext);
959 consumed += RegExp.leftContext.length;
961 var url = this.htmlEscape(RegExp.lastMatch);
964 // If no protocol was specified, try to guess a reasonable one.
965 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
966 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
967 var slash = url.indexOf('/');
968 var at = url.indexOf('@');
969 var question = url.indexOf('?');
971 (at < question || question < 0) &&
972 (slash < 0 || (question > 0 && slash > question))) {
973 fullUrl = 'mailto:' + url;
975 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
980 inner += '<a target="vt100Link" href="' + fullUrl +
982 consumed += RegExp.lastMatch.length;
983 s = s.substr(consumed);
984 if (!this.urlRE.test(s)) {
985 if (RegExp.rightContext != null) {
986 inner += this.htmlEscape(RegExp.rightContext);
991 elem.innerHTML = inner;
995 // Updating the content of an element is an expensive operation. It actually
996 // pays off to first check whether the element is still unchanged.
997 if (typeof elem.textContent == 'undefined') {
998 if (elem.innerText != s) {
1002 // Very old versions of IE do not allow setting innerText. Instead,
1003 // remove all children, by setting innerHTML and then set the text
1004 // using DOM methods.
1005 elem.innerHTML = '';
1006 elem.appendChild(document.createTextNode(
1007 this.replaceChar(s, ' ', '\u00A0')));
1011 if (elem.textContent != s) {
1012 elem.textContent = s;
1017 VT100.prototype.insertBlankLine = function(y, color, style) {
1018 // Insert a blank line a position y. This method ignores the scrollback
1019 // buffer. The caller has to add the length of the scrollback buffer to
1020 // the position, if necessary.
1021 // If the position is larger than the number of current lines, this
1022 // method just adds a new line right after the last existing one. It does
1023 // not add any missing lines in between. It is the caller's responsibility
1026 color = 'ansi0 bgAnsi15';
1032 if (color != 'ansi0 bgAnsi15' && !style) {
1033 line = document.createElement('pre');
1034 this.setTextContent(line, '\n');
1036 line = document.createElement('div');
1037 var span = document.createElement('span');
1038 span.style.cssText = style;
1039 span.style.className = color;
1040 this.setTextContent(span, this.spaces(this.terminalWidth));
1041 line.appendChild(span);
1043 line.style.height = this.cursorHeight + 'px';
1044 var console = this.console[this.currentScreen];
1045 if (console.childNodes.length > y) {
1046 console.insertBefore(line, console.childNodes[y]);
1048 console.appendChild(line);
1052 VT100.prototype.updateWidth = function() {
1053 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1055 return this.terminalWidth;
1058 VT100.prototype.updateHeight = function() {
1059 // We want to be able to display either a terminal window that fills the
1060 // entire browser window, or a terminal window that is contained in a
1061 // <div> which is embededded somewhere in the web page.
1062 if (this.isEmbedded) {
1063 // Embedded terminal. Use size of the containing <div> (id="vt100").
1064 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1067 // Use the full browser window.
1068 this.terminalHeight = Math.floor(((window.innerHeight ||
1069 document.documentElement.clientHeight ||
1070 document.body.clientHeight)-1)/
1073 return this.terminalHeight;
1076 VT100.prototype.updateNumScrollbackLines = function() {
1077 var scrollback = Math.floor(
1078 this.console[this.currentScreen].offsetHeight /
1079 this.cursorHeight) -
1080 this.terminalHeight;
1081 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1082 return this.numScrollbackLines;
1085 VT100.prototype.truncateLines = function(width) {
1089 for (var line = this.console[this.currentScreen].firstChild; line;
1090 line = line.nextSibling) {
1091 if (line.tagName == 'DIV') {
1094 // Traverse current line and truncate it once we saw "width" characters
1095 for (var span = line.firstChild; span;
1096 span = span.nextSibling) {
1097 var s = this.getTextContent(span);
1099 if (x + l > width) {
1100 this.setTextContent(span, s.substr(0, width - x));
1101 while (span.nextSibling) {
1102 line.removeChild(line.lastChild);
1108 // Prune white space from the end of the current line
1109 var span = line.lastChild;
1111 span.className == 'ansi0 bgAnsi15' &&
1112 !span.style.cssText.length) {
1113 // Scan backwards looking for first non-space character
1114 var s = this.getTextContent(span);
1115 for (var i = s.length; i--; ) {
1116 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1117 if (i+1 != s.length) {
1118 this.setTextContent(s.substr(0, i+1));
1126 span = span.previousSibling;
1128 // Remove blank <span>'s from end of line
1129 line.removeChild(sibling);
1131 // Remove entire line (i.e. <div>), if empty
1132 var blank = document.createElement('pre');
1133 blank.style.height = this.cursorHeight + 'px';
1134 this.setTextContent(blank, '\n');
1135 line.parentNode.replaceChild(blank, line);
1143 VT100.prototype.putString = function(x, y, text, color, style) {
1145 color = 'ansi0 bgAnsi15';
1150 var yIdx = y + this.numScrollbackLines;
1156 var console = this.console[this.currentScreen];
1157 if (!text.length && (yIdx >= console.childNodes.length ||
1158 console.childNodes[yIdx].tagName != 'DIV')) {
1159 // Positioning cursor to a blank location
1162 // Create missing blank lines at end of page
1163 while (console.childNodes.length <= yIdx) {
1164 // In order to simplify lookups, we want to make sure that each line
1165 // is represented by exactly one element (and possibly a whole bunch of
1167 // For non-blank lines, we can create a <div> containing one or more
1168 // <span>s. For blank lines, this fails as browsers tend to optimize them
1169 // away. But fortunately, a <pre> tag containing a newline character
1170 // appears to work for all browsers (a would also work, but then
1171 // copying from the browser window would insert superfluous spaces into
1173 this.insertBlankLine(yIdx);
1175 line = console.childNodes[yIdx];
1177 // If necessary, promote blank '\n' line to a <div> tag
1178 if (line.tagName != 'DIV') {
1179 var div = document.createElement('div');
1180 div.style.height = this.cursorHeight + 'px';
1181 div.innerHTML = '<span></span>';
1182 console.replaceChild(div, line);
1186 // Scan through list of <span>'s until we find the one where our text
1188 span = line.firstChild;
1190 while (span.nextSibling && xPos < x) {
1191 len = this.getTextContent(span).length;
1192 if (xPos + len > x) {
1196 span = span.nextSibling;
1200 // If current <span> is not long enough, pad with spaces or add new
1202 s = this.getTextContent(span);
1203 var oldColor = span.className;
1204 var oldStyle = span.style.cssText;
1205 if (xPos + s.length < x) {
1206 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1207 span = document.createElement('span');
1208 line.appendChild(span);
1209 span.className = 'ansi0 bgAnsi15';
1210 span.style.cssText = '';
1211 oldColor = 'ansi0 bgAnsi15';
1218 } while (xPos + s.length < x);
1221 // If styles do not match, create a new <span>
1222 var del = text.length - s.length + x - xPos;
1223 if (oldColor != color ||
1224 (oldStyle != style && (oldStyle || style))) {
1226 // Replacing text at beginning of existing <span>
1227 if (text.length >= s.length) {
1228 // New text is equal or longer than existing text
1231 // Insert new <span> before the current one, then remove leading
1232 // part of existing <span>, adjust style of new <span>, and finally
1234 sibling = document.createElement('span');
1235 line.insertBefore(sibling, span);
1236 this.setTextContent(span, s.substr(text.length));
1241 // Replacing text some way into the existing <span>
1242 var remainder = s.substr(x + text.length - xPos);
1243 this.setTextContent(span, s.substr(0, x - xPos));
1245 sibling = document.createElement('span');
1246 if (span.nextSibling) {
1247 line.insertBefore(sibling, span.nextSibling);
1249 if (remainder.length) {
1250 sibling = document.createElement('span');
1251 sibling.className = oldColor;
1252 sibling.style.cssText = oldStyle;
1253 this.setTextContent(sibling, remainder);
1254 line.insertBefore(sibling, span.nextSibling);
1257 line.appendChild(sibling);
1259 if (remainder.length) {
1260 sibling = document.createElement('span');
1261 sibling.className = oldColor;
1262 sibling.style.cssText = oldStyle;
1263 this.setTextContent(sibling, remainder);
1264 line.appendChild(sibling);
1269 span.className = color;
1270 span.style.cssText = style;
1272 // Overwrite (partial) <span> with new text
1273 s = s.substr(0, x - xPos) +
1275 s.substr(x + text.length - xPos);
1277 this.setTextContent(span, s);
1280 // Delete all subsequent <span>'s that have just been overwritten
1281 sibling = span.nextSibling;
1282 while (del > 0 && sibling) {
1283 s = this.getTextContent(sibling);
1286 line.removeChild(sibling);
1288 sibling = span.nextSibling;
1290 this.setTextContent(sibling, s.substr(del));
1295 // Merge <span> with next sibling, if styles are identical
1296 if (sibling && span.className == sibling.className &&
1297 span.style.cssText == sibling.style.cssText) {
1298 this.setTextContent(span,
1299 this.getTextContent(span) +
1300 this.getTextContent(sibling));
1301 line.removeChild(sibling);
1307 this.cursorX = x + text.length;
1308 if (this.cursorX >= this.terminalWidth) {
1309 this.cursorX = this.terminalWidth - 1;
1310 if (this.cursorX < 0) {
1316 if (!this.cursor.style.visibility) {
1317 var idx = this.cursorX - xPos;
1319 // If we are in a non-empty line, take the cursor Y position from the
1320 // other elements in this line. If dealing with broken, non-proportional
1321 // fonts, this is likely to yield better results.
1322 pixelY = span.offsetTop +
1323 span.offsetParent.offsetTop;
1324 s = this.getTextContent(span);
1325 var nxtIdx = idx - s.length;
1327 this.setTextContent(this.cursor, s.charAt(idx));
1328 pixelX = span.offsetLeft +
1329 idx*span.offsetWidth / s.length;
1332 pixelX = span.offsetLeft + span.offsetWidth;
1334 if (span.nextSibling) {
1335 s = this.getTextContent(span.nextSibling);
1336 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1338 pixelX = span.nextSibling.offsetLeft +
1339 nxtIdx*span.offsetWidth / s.length;
1342 this.setTextContent(this.cursor, ' ');
1346 this.setTextContent(this.cursor, ' ');
1350 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
1352 this.setTextContent(this.space, this.spaces(this.cursorX));
1353 this.cursor.style.left = this.space.offsetWidth +
1354 console.offsetLeft + 'px';
1356 this.cursorY = yIdx - this.numScrollbackLines;
1358 this.cursor.style.top = pixelY + 'px';
1360 this.cursor.style.top = yIdx*this.cursorHeight +
1361 console.offsetTop + 'px';
1365 // Merge <span> with previous sibling, if styles are identical
1366 if ((sibling = span.previousSibling) &&
1367 span.className == sibling.className &&
1368 span.style.cssText == sibling.style.cssText) {
1369 this.setTextContent(span,
1370 this.getTextContent(sibling) +
1371 this.getTextContent(span));
1372 line.removeChild(sibling);
1375 // Prune white space from the end of the current line
1376 span = line.lastChild;
1378 span.className == 'ansi0 bgAnsi15' &&
1379 !span.style.cssText.length) {
1380 // Scan backwards looking for first non-space character
1381 s = this.getTextContent(span);
1382 for (var i = s.length; i--; ) {
1383 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1384 if (i+1 != s.length) {
1385 this.setTextContent(s.substr(0, i+1));
1393 span = span.previousSibling;
1395 // Remove blank <span>'s from end of line
1396 line.removeChild(sibling);
1398 // Remove entire line (i.e. <div>), if empty
1399 var blank = document.createElement('pre');
1400 blank.style.height = this.cursorHeight + 'px';
1401 this.setTextContent(blank, '\n');
1402 line.parentNode.replaceChild(blank, line);
1409 VT100.prototype.gotoXY = function(x, y) {
1410 if (x >= this.terminalWidth) {
1411 x = this.terminalWidth - 1;
1417 if (this.offsetMode) {
1422 maxY = this.terminalHeight;
1430 this.putString(x, y, '', undefined);
1431 this.needWrap = false;
1434 VT100.prototype.gotoXaY = function(x, y) {
1435 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1438 VT100.prototype.refreshInvertedState = function() {
1439 if (this.isInverted) {
1440 this.scrollable.className += ' inverted';
1442 this.scrollable.className = this.scrollable.className.
1443 replace(/ *inverted/, '');
1447 VT100.prototype.enableAlternateScreen = function(state) {
1448 // Don't do anything, if we are already on the desired screen
1449 if ((state ? 1 : 0) == this.currentScreen) {
1450 // Calling the resizer is not actually necessary. But it is a good way
1451 // of resetting state that might have gotten corrupted.
1456 // We save the full state of the normal screen, when we switch away from it.
1457 // But for the alternate screen, no saving is necessary. We always reset
1458 // it when we switch to it.
1463 // Display new screen, and initialize state (the resizer does that for us).
1464 this.currentScreen = state ? 1 : 0;
1465 this.console[1-this.currentScreen].style.display = 'none';
1466 this.console[this.currentScreen].style.display = '';
1469 // If we switched to the alternate screen, reset it completely. Otherwise,
1470 // restore the saved state.
1473 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1475 this.restoreCursor();
1479 VT100.prototype.hideCursor = function() {
1480 var hidden = this.cursor.style.visibility == 'hidden';
1482 this.cursor.style.visibility = 'hidden';
1488 VT100.prototype.showCursor = function(x, y) {
1489 if (this.cursor.style.visibility) {
1490 this.cursor.style.visibility = '';
1491 this.putString(x == undefined ? this.cursorX : x,
1492 y == undefined ? this.cursorY : y,
1499 VT100.prototype.scrollBack = function() {
1500 var i = this.scrollable.scrollTop -
1501 this.scrollable.clientHeight;
1502 this.scrollable.scrollTop = i < 0 ? 0 : i;
1505 VT100.prototype.scrollFore = function() {
1506 var i = this.scrollable.scrollTop +
1507 this.scrollable.clientHeight;
1508 this.scrollable.scrollTop = i > this.numScrollbackLines *
1509 this.cursorHeight + 1
1510 ? this.numScrollbackLines *
1511 this.cursorHeight + 1
1515 VT100.prototype.spaces = function(i) {
1523 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
1528 if (w > this.terminalWidth) {
1529 w = this.terminalWidth;
1531 if ((w -= x) <= 0) {
1538 if (h > this.terminalHeight) {
1539 h = this.terminalHeight;
1541 if ((h -= y) <= 0) {
1545 // Special case the situation where we clear the entire screen, and we do
1546 // not have a scrollback buffer. In that case, we should just remove all
1548 if (!this.numScrollbackLines &&
1549 w == this.terminalWidth && h == this.terminalHeight &&
1550 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
1551 var console = this.console[this.currentScreen];
1552 while (console.lastChild) {
1553 console.removeChild(console.lastChild);
1555 this.putString(this.cursorX, this.cursorY, '', undefined);
1557 var hidden = this.hideCursor();
1558 var cx = this.cursorX;
1559 var cy = this.cursorY;
1560 var s = this.spaces(w);
1561 for (var i = y+h; i-- > y; ) {
1562 this.putString(x, i, s, color, style);
1564 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1568 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1570 var className = [ ];
1572 var console = this.console[this.currentScreen];
1573 if (sY >= console.childNodes.length) {
1574 text[0] = this.spaces(w);
1575 className[0] = undefined;
1576 style[0] = undefined;
1578 var line = console.childNodes[sY];
1579 if (line.tagName != 'DIV' || !line.childNodes.length) {
1580 text[0] = this.spaces(w);
1581 className[0] = undefined;
1582 style[0] = undefined;
1585 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1586 var s = this.getTextContent(span);
1589 var o = sX > x ? sX - x : 0;
1590 text[text.length] = s.substr(o, w);
1591 className[className.length] = span.className;
1592 style[style.length] = span.style.cssText;
1598 text[text.length] = this.spaces(w);
1599 className[className.length] = undefined;
1600 style[style.length] = undefined;
1604 var hidden = this.hideCursor();
1605 var cx = this.cursorX;
1606 var cy = this.cursorY;
1607 for (var i = 0; i < text.length; i++) {
1610 color = className[i];
1612 color = 'ansi0 bgAnsi15';
1614 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
1615 dX += text[i].length;
1617 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1620 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
1622 var left = incX < 0 ? -incX : 0;
1623 var right = incX > 0 ? incX : 0;
1624 var up = incY < 0 ? -incY : 0;
1625 var down = incY > 0 ? incY : 0;
1627 // Clip region against terminal size
1628 var dontScroll = null;
1633 if (w > this.terminalWidth - right) {
1634 w = this.terminalWidth - right;
1636 if ((w -= x) <= 0) {
1643 if (h > this.terminalHeight - down) {
1644 h = this.terminalHeight - down;
1650 if (style && style.indexOf('underline')) {
1651 // Different terminal emulators disagree on the attributes that
1652 // are used for scrolling. The consensus seems to be, never to
1653 // fill with underlined spaces. N.B. this is different from the
1654 // cases when the user blanks a region. User-initiated blanking
1655 // always fills with all of the current attributes.
1656 style = style.replace(/text-decoration:underline;/, '');
1659 // Compute current scroll position
1660 var scrollPos = this.numScrollbackLines -
1661 (this.scrollable.scrollTop-1) / this.cursorHeight;
1663 // Determine original cursor position. Hide cursor temporarily to avoid
1664 // visual artifacts.
1665 var hidden = this.hideCursor();
1666 var cx = this.cursorX;
1667 var cy = this.cursorY;
1668 var console = this.console[this.currentScreen];
1670 if (!incX && !x && w == this.terminalWidth) {
1671 // Scrolling entire lines
1674 if (!this.currentScreen && y == -incY &&
1675 h == this.terminalHeight + incY) {
1676 // Scrolling up with adding to the scrollback buffer. This is only
1677 // possible if there are at least as many lines in the console,
1678 // as the terminal is high
1679 while (console.childNodes.length < this.terminalHeight) {
1680 this.insertBlankLine(this.terminalHeight);
1683 // Add new lines at bottom in order to force scrolling
1684 for (var i = 0; i < y; i++) {
1685 this.insertBlankLine(console.childNodes.length, color, style);
1688 // Adjust the number of lines in the scrollback buffer by
1689 // removing excess entries.
1690 this.updateNumScrollbackLines();
1691 while (this.numScrollbackLines >
1692 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1693 console.removeChild(console.firstChild);
1694 this.numScrollbackLines--;
1697 // Mark lines in the scrollback buffer, so that they do not get
1699 for (var i = this.numScrollbackLines, j = -incY;
1700 i-- > 0 && j-- > 0; ) {
1701 console.childNodes[i].className = 'scrollback';
1704 // Scrolling up without adding to the scrollback buffer.
1707 console.childNodes.length >
1708 this.numScrollbackLines + y + incY; ) {
1709 console.removeChild(console.childNodes[
1710 this.numScrollbackLines + y + incY]);
1713 // If we used to have a scrollback buffer, then we must make sure
1714 // that we add back blank lines at the bottom of the terminal.
1715 // Similarly, if we are scrolling in the middle of the screen,
1716 // we must add blank lines to ensure that the bottom of the screen
1717 // does not move up.
1718 if (this.numScrollbackLines > 0 ||
1719 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1720 for (var i = -incY; i-- > 0; ) {
1721 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1730 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1731 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1733 for (var i = incY; i--; ) {
1734 this.insertBlankLine(this.numScrollbackLines + y, color, style);
1738 // Scrolling partial lines
1740 // Scrolling up or horizontally within a line
1741 for (var i = y + this.numScrollbackLines;
1742 i < y + this.numScrollbackLines + h;
1744 this.copyLineSegment(x + incX, i + incY, x, i, w);
1748 for (var i = y + this.numScrollbackLines + h;
1749 i-- > y + this.numScrollbackLines; ) {
1750 this.copyLineSegment(x + incX, i + incY, x, i, w);
1754 // Clear blank regions
1756 this.clearRegion(x, y, incX, h, color, style);
1757 } else if (incX < 0) {
1758 this.clearRegion(x + w + incX, y, -incX, h, color, style);
1761 this.clearRegion(x, y, w, incY, color, style);
1762 } else if (incY < 0) {
1763 this.clearRegion(x, y + h + incY, w, -incY, color, style);
1767 // Reset scroll position
1768 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1769 this.cursorHeight + 1;
1771 // Move cursor back to its original position
1772 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1776 VT100.prototype.copy = function(selection) {
1777 if (selection == undefined) {
1778 selection = this.selection();
1780 this.internalClipboard = undefined;
1781 if (selection.length) {
1784 this.cliphelper.value = selection;
1785 this.cliphelper.select();
1786 this.cliphelper.createTextRange().execCommand('copy');
1788 this.internalClipboard = selection;
1790 this.cliphelper.value = '';
1794 VT100.prototype.copyLast = function() {
1795 // Opening the context menu can remove the selection. We try to prevent this
1796 // from happening, but that is not possible for all browsers. So, instead,
1797 // we compute the selection before showing the menu.
1798 this.copy(this.lastSelection);
1801 VT100.prototype.pasteFnc = function() {
1802 var clipboard = undefined;
1803 if (this.internalClipboard != undefined) {
1804 clipboard = this.internalClipboard;
1807 this.cliphelper.value = '';
1808 this.cliphelper.createTextRange().execCommand('paste');
1809 clipboard = this.cliphelper.value;
1813 this.cliphelper.value = '';
1814 if (clipboard && this.menu.style.visibility == 'hidden') {
1816 this.keysPressed('' + clipboard);
1823 VT100.prototype.toggleUTF = function() {
1824 this.utfEnabled = !this.utfEnabled;
1827 VT100.prototype.toggleBell = function() {
1828 this.visualBell = !this.visualBell;
1831 VT100.prototype.about = function() {
1832 alert("VT100 Terminal Emulator " + "2.9 (revision 168)" +
1833 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1834 "For more information check http://shellinabox.com");
1837 VT100.prototype.hideContextMenu = function() {
1838 this.menu.style.visibility = 'hidden';
1839 this.menu.style.top = '-100px';
1840 this.menu.style.left = '-100px';
1841 this.menu.style.width = '0px';
1842 this.menu.style.height = '0px';
1845 VT100.prototype.extendContextMenu = function(entries, actions) {
1848 VT100.prototype.showContextMenu = function(x, y) {
1849 this.menu.innerHTML =
1850 '<table class="popup" ' +
1851 'cellpadding="0" cellspacing="0">' +
1853 '<ul id="menuentries">' +
1854 '<li id="beginclipboard">Copy</li>' +
1855 '<li id="endclipboard">Paste</li>' +
1857 '<li id="reset">Reset</li>' +
1859 '<li id="beginconfig">' +
1860 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
1862 '<li id="endconfig">' +
1863 (this.visualBell ? '<img src="enabled.gif" />' : '') +
1865 (this.usercss.firstChild ?
1866 '<hr id="beginusercss" />' +
1867 this.usercss.innerHTML +
1868 '<hr id="endusercss" />' :
1870 '<li id="about">About...</li>' +
1875 var popup = this.menu.firstChild;
1876 var menuentries = this.getChildById(popup, 'menuentries');
1878 // Determine menu entries that should be disabled
1879 this.lastSelection = this.selection();
1880 if (!this.lastSelection.length) {
1881 menuentries.firstChild.className
1884 var p = this.pasteFnc();
1886 menuentries.childNodes[1].className
1890 // Actions for default items
1891 var actions = [ this.copyLast, p, this.reset,
1892 this.toggleUTF, this.toggleBell ];
1894 // Actions for user CSS styles (if any)
1895 for (var i = 0; i < this.usercssActions.length; ++i) {
1896 actions[actions.length] = this.usercssActions[i];
1898 actions[actions.length] = this.about;
1900 // Allow subclasses to dynamically add entries to the context menu
1901 this.extendContextMenu(menuentries, actions);
1903 // Hook up event listeners
1904 for (var node = menuentries.firstChild, i = 0; node;
1905 node = node.nextSibling) {
1906 if (node.tagName == 'LI') {
1907 if (node.className != 'disabled') {
1908 this.addListener(node, 'mouseover',
1909 function(vt100, node) {
1911 node.className = 'hover';
1914 this.addListener(node, 'mouseout',
1915 function(vt100, node) {
1917 node.className = '';
1920 this.addListener(node, 'mousedown',
1921 function(vt100, action) {
1922 return function(event) {
1923 vt100.hideContextMenu();
1925 return vt100.cancelEvent(event || window.event);
1927 }(this, actions[i]));
1928 this.addListener(node, 'mouseup',
1930 return function(event) {
1931 return vt100.cancelEvent(event || window.event);
1934 this.addListener(node, 'mouseclick',
1936 return function(event) {
1937 return vt100.cancelEvent(event || window.event);
1945 // Position menu next to the mouse pointer
1946 if (x + popup.clientWidth > this.container.offsetWidth) {
1947 x = this.container.offsetWidth - popup.clientWidth;
1952 if (y + popup.clientHeight > this.container.offsetHeight) {
1953 y = this.container.offsetHeight-popup.clientHeight;
1958 popup.style.left = x + 'px';
1959 popup.style.top = y + 'px';
1961 // Block all other interactions with the terminal emulator
1962 this.menu.style.left = '0px';
1963 this.menu.style.top = '0px';
1964 this.menu.style.width = this.container.offsetWidth + 'px';
1965 this.menu.style.height = this.container.offsetHeight + 'px';
1966 this.addListener(this.menu, 'click', function(vt100) {
1968 vt100.hideContextMenu();
1973 this.menu.style.visibility = '';
1976 VT100.prototype.keysPressed = function(ch) {
1977 for (var i = 0; i < ch.length; i++) {
1978 var c = ch.charCodeAt(i);
1979 this.vt100(c >= 7 && c <= 15 ||
1980 c == 24 || c == 26 || c == 27 || c >= 32
1981 ? String.fromCharCode(c) : '<' + c + '>');
1985 VT100.prototype.handleKey = function(event) {
1987 if (typeof event.charCode != 'undefined') {
1988 // non-IE keypress events have a translated charCode value. Also, our
1989 // fake events generated when receiving keydown events include this data
1991 ch = event.charCode;
1992 key = event.keyCode;
1994 // When sending a keypress event, IE includes the translated character
1995 // code in the keyCode field.
2000 // Apply modifier keys (ctrl and shift)
2003 if (event.ctrlKey) {
2004 if (ch >= 32 && ch <= 127) {
2008 if (event.shiftKey) {
2009 if (ch >= 97 && ch <= 122) {
2013 if (ch >= 65 && ch <= 90) {
2022 // By this point, "ch" is either defined and contains the character code, or
2023 // it is undefined and "key" defines the code of a function key
2024 if (ch != undefined) {
2025 ch = String.fromCharCode(ch);
2026 this.scrollable.scrollTop = this.numScrollbackLines *
2027 this.cursorHeight + 1;
2029 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2030 // Many programs have difficulties dealing with parametrized escape
2031 // sequences for function keys. Thus, if ALT is the only modifier
2032 // key, return Emacs-style keycodes for commonly used keys.
2034 case 33: /* Page Up */ ch = '\u001B<'; break;
2035 case 34: /* Page Down */ ch = '\u001B>'; break;
2036 case 37: /* Left */ ch = '\u001Bb'; break;
2037 case 38: /* Up */ ch = '\u001Bp'; break;
2038 case 39: /* Right */ ch = '\u001Bf'; break;
2039 case 40: /* Down */ ch = '\u001Bn'; break;
2040 case 46: /* Delete */ ch = '\u001Bd'; break;
2043 } else if (event.shiftKey && !event.ctrlKey &&
2044 !event.altKey && !event.metaKey) {
2046 case 33: /* Page Up */ this.scrollBack(); return;
2047 case 34: /* Page Down */ this.scrollFore(); return;
2051 if (ch == undefined) {
2053 case 8: /* Backspace */ ch = '\u007f'; break;
2054 case 9: /* Tab */ ch = '\u0009'; break;
2055 case 10: /* Return */ ch = '\u000A'; break;
2056 case 13: /* Enter */ ch = this.crLfMode ?
2057 '\r\n' : '\r'; break;
2058 case 16: /* Shift */ return;
2059 case 17: /* Ctrl */ return;
2060 case 18: /* Alt */ return;
2061 case 19: /* Break */ return;
2062 case 20: /* Caps Lock */ return;
2063 case 27: /* Escape */ ch = '\u001B'; break;
2064 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2065 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2066 case 35: /* End */ ch = '\u001BOF'; break;
2067 case 36: /* Home */ ch = '\u001BOH'; break;
2068 case 37: /* Left */ ch = this.cursorKeyMode ?
2069 '\u001BOD' : '\u001B[D'; break;
2070 case 38: /* Up */ ch = this.cursorKeyMode ?
2071 '\u001BOA' : '\u001B[A'; break;
2072 case 39: /* Right */ ch = this.cursorKeyMode ?
2073 '\u001BOC' : '\u001B[C'; break;
2074 case 40: /* Down */ ch = this.cursorKeyMode ?
2075 '\u001BOB' : '\u001B[B'; break;
2076 case 45: /* Insert */ ch = '\u001B[2~'; break;
2077 case 46: /* Delete */ ch = '\u001B[3~'; break;
2078 case 91: /* Left Window */ return;
2079 case 92: /* Right Window */ return;
2080 case 93: /* Select */ return;
2081 case 96: /* 0 */ ch = '0'; break;
2082 case 97: /* 1 */ ch = '1'; break;
2083 case 98: /* 2 */ ch = '2'; break;
2084 case 99: /* 3 */ ch = '3'; break;
2085 case 100: /* 4 */ ch = '4'; break;
2086 case 101: /* 5 */ ch = '5'; break;
2087 case 102: /* 6 */ ch = '6'; break;
2088 case 103: /* 7 */ ch = '7'; break;
2089 case 104: /* 8 */ ch = '8'; break;
2090 case 105: /* 9 */ ch = '9'; break;
2091 case 106: /* * */ ch = '*'; break;
2092 case 107: /* + */ ch = '+'; break;
2093 case 109: /* - */ ch = '-'; break;
2094 case 110: /* . */ ch = '.'; break;
2095 case 111: /* / */ ch = '/'; break;
2096 case 112: /* F1 */ ch = '\u001BOP'; break;
2097 case 113: /* F2 */ ch = '\u001BOQ'; break;
2098 case 114: /* F3 */ ch = '\u001BOR'; break;
2099 case 115: /* F4 */ ch = '\u001BOS'; break;
2100 case 116: /* F5 */ ch = '\u001B[15~'; break;
2101 case 117: /* F6 */ ch = '\u001B[17~'; break;
2102 case 118: /* F7 */ ch = '\u001B[18~'; break;
2103 case 119: /* F8 */ ch = '\u001B[19~'; break;
2104 case 120: /* F9 */ ch = '\u001B[20~'; break;
2105 case 121: /* F10 */ ch = '\u001B[21~'; break;
2106 case 122: /* F11 */ ch = '\u001B[23~'; break;
2107 case 123: /* F12 */ ch = '\u001B[24~'; break;
2108 case 144: /* Num Lock */ return;
2109 case 145: /* Scroll Lock */ return;
2112 this.scrollable.scrollTop = this.numScrollbackLines *
2113 this.cursorHeight + 1;
2117 // "ch" now contains the sequence of keycodes to send. But we might still
2118 // have to apply the effects of modifier keys.
2119 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2120 var start, digit, part1, part2;
2121 if ((start = ch.substr(0, 2)) == '\u001B[') {
2123 part1.length < ch.length &&
2124 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2125 part1 = ch.substr(0, part1.length + 1);
2127 part2 = ch.substr(part1.length);
2128 if (part1.length > 2) {
2131 } else if (start == '\u001BO') {
2133 part2 = ch.substr(2);
2135 if (part1 != undefined) {
2137 ((event.shiftKey ? 1 : 0) +
2138 (event.altKey|event.metaKey ? 2 : 0) +
2139 (event.ctrlKey ? 4 : 0)) +
2141 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2146 if (this.menu.style.visibility == 'hidden') {
2147 // this.vt100('R: c=');
2148 // for (var i = 0; i < ch.length; i++)
2149 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2150 // this.vt100('\r\n');
2151 this.keysPressed(ch);
2155 VT100.prototype.inspect = function(o, d) {
2156 if (d == undefined) {
2160 if (typeof o == 'object' && ++d < 2) {
2163 rc += this.spaces(d * 2) + i + ' -> ';
2165 rc += this.inspect(o[i], d);
2167 rc += '?' + '?' + '?\r\n';
2172 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2177 VT100.prototype.checkComposedKeys = function(event) {
2178 // Composed keys (at least on Linux) do not generate normal events.
2179 // Instead, they get entered into the text field. We normally catch
2180 // this on the next keyup event.
2181 var s = this.input.value;
2183 this.input.value = '';
2184 if (this.menu.style.visibility == 'hidden') {
2185 this.keysPressed(s);
2190 VT100.prototype.fixEvent = function(event) {
2191 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2192 // is used as a second-level selector, clear the modifier bits before
2193 // handling the event.
2194 if (event.ctrlKey && event.altKey) {
2196 fake.charCode = event.charCode;
2197 fake.keyCode = event.keyCode;
2198 fake.ctrlKey = false;
2199 fake.shiftKey = event.shiftKey;
2200 fake.altKey = false;
2201 fake.metaKey = event.metaKey;
2205 // Some browsers fail to translate keys, if both shift and alt/meta is
2206 // pressed at the same time. We try to translate those cases, but that
2207 // only works for US keyboard layouts.
2208 if (event.shiftKey) {
2211 switch (this.lastNormalKeyDownEvent.keyCode) {
2212 case 39: /* ' -> " */ u = 39; s = 34; break;
2213 case 44: /* , -> < */ u = 44; s = 60; break;
2214 case 45: /* - -> _ */ u = 45; s = 95; break;
2215 case 46: /* . -> > */ u = 46; s = 62; break;
2216 case 47: /* / -> ? */ u = 47; s = 63; break;
2218 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2219 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2220 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2221 case 51: /* 3 -> # */ u = 51; s = 35; break;
2222 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2223 case 53: /* 5 -> % */ u = 53; s = 37; break;
2224 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2225 case 55: /* 7 -> & */ u = 55; s = 38; break;
2226 case 56: /* 8 -> * */ u = 56; s = 42; break;
2227 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2229 case 59: /* ; -> : */ u = 59; s = 58; break;
2230 case 61: /* = -> + */ u = 61; s = 43; break;
2231 case 91: /* [ -> { */ u = 91; s = 123; break;
2232 case 92: /* \ -> | */ u = 92; s = 124; break;
2233 case 93: /* ] -> } */ u = 93; s = 125; break;
2234 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2236 case 109: /* - -> _ */ u = 45; s = 95; break;
2237 case 111: /* / -> ? */ u = 47; s = 63; break;
2239 case 186: /* ; -> : */ u = 59; s = 58; break;
2240 case 187: /* = -> + */ u = 61; s = 43; break;
2241 case 188: /* , -> < */ u = 44; s = 60; break;
2242 case 189: /* - -> _ */ u = 45; s = 95; break;
2243 case 190: /* . -> > */ u = 46; s = 62; break;
2244 case 191: /* / -> ? */ u = 47; s = 63; break;
2245 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2246 case 219: /* [ -> { */ u = 91; s = 123; break;
2247 case 220: /* \ -> | */ u = 92; s = 124; break;
2248 case 221: /* ] -> } */ u = 93; s = 125; break;
2249 case 222: /* ' -> " */ u = 39; s = 34; break;
2252 if (s && (event.charCode == u || event.charCode == 0)) {
2255 fake.keyCode = event.keyCode;
2256 fake.ctrlKey = event.ctrlKey;
2257 fake.shiftKey = event.shiftKey;
2258 fake.altKey = event.altKey;
2259 fake.metaKey = event.metaKey;
2266 VT100.prototype.keyDown = function(event) {
2267 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2268 // (event.shiftKey || event.ctrlKey || event.altKey ||
2269 // event.metaKey ? ', ' +
2270 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2271 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2273 this.checkComposedKeys(event);
2274 this.lastKeyPressedEvent = undefined;
2275 this.lastKeyDownEvent = undefined;
2276 this.lastNormalKeyDownEvent = event;
2279 event.keyCode == 32 ||
2280 event.keyCode >= 48 && event.keyCode <= 57 ||
2281 event.keyCode >= 65 && event.keyCode <= 90;
2284 event.keyCode >= 96 && event.keyCode <= 105 ||
2285 event.keyCode == 226;
2288 event.keyCode == 59 || event.keyCode == 61 ||
2289 event.keyCode == 106 || event.keyCode == 107 ||
2290 event.keyCode >= 109 && event.keyCode <= 111 ||
2291 event.keyCode >= 186 && event.keyCode <= 192 ||
2292 event.keyCode >= 219 && event.keyCode <= 222 ||
2293 event.keyCode == 252;
2295 if (navigator.appName == 'Konqueror') {
2296 normalKey |= event.keyCode < 128;
2301 // We normally prefer to look at keypress events, as they perform the
2302 // translation from keyCode to charCode. This is important, as the
2303 // translation is locale-dependent.
2304 // But for some keys, we must intercept them during the keydown event,
2305 // as they would otherwise get interpreted by the browser.
2306 // Even, when doing all of this, there are some keys that we can never
2307 // intercept. This applies to some of the menu navigation keys in IE.
2308 // In fact, we see them, but we cannot stop IE from seeing them, too.
2309 if ((event.charCode || event.keyCode) &&
2310 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2312 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2313 // interpret this sequence ourselves, as some keyboard layouts use
2314 // it for second-level layouts.
2315 !(event.ctrlKey && event.altKey)) ||
2316 this.catchModifiersEarly && normalKey && !alphNumKey &&
2317 (event.ctrlKey || event.altKey || event.metaKey) ||
2319 this.lastKeyDownEvent = event;
2321 fake.ctrlKey = event.ctrlKey;
2322 fake.shiftKey = event.shiftKey;
2323 fake.altKey = event.altKey;
2324 fake.metaKey = event.metaKey;
2326 fake.charCode = event.keyCode;
2330 fake.keyCode = event.keyCode;
2331 if (!alphNumKey && event.shiftKey) {
2332 fake = this.fixEvent(fake);
2336 this.handleKey(fake);
2337 this.lastNormalKeyDownEvent = undefined;
2340 // For non-IE browsers
2341 event.stopPropagation();
2342 event.preventDefault();
2347 event.cancelBubble = true;
2348 event.returnValue = false;
2358 VT100.prototype.keyPressed = function(event) {
2359 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2360 // (event.shiftKey || event.ctrlKey || event.altKey ||
2361 // event.metaKey ? ', ' +
2362 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2363 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2365 if (this.lastKeyDownEvent) {
2366 // If we already processed the key on keydown, do not process it
2367 // again here. Ideally, the browser should not even have generated a
2368 // keypress event in this case. But that does not appear to always work.
2369 this.lastKeyDownEvent = undefined;
2371 this.handleKey(event.altKey || event.metaKey
2372 ? this.fixEvent(event) : event);
2376 // For non-IE browsers
2377 event.preventDefault();
2383 event.cancelBubble = true;
2384 event.returnValue = false;
2389 this.lastNormalKeyDownEvent = undefined;
2390 this.lastKeyPressedEvent = event;
2394 VT100.prototype.keyUp = function(event) {
2395 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2396 // (event.shiftKey || event.ctrlKey || event.altKey ||
2397 // event.metaKey ? ', ' +
2398 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2399 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2401 if (this.lastKeyPressedEvent) {
2402 // The compose key on Linux occasionally confuses the browser and keeps
2403 // inserting bogus characters into the input field, even if just a regular
2404 // key has been pressed. Detect this case and drop the bogus characters.
2406 event.srcElement).value = '';
2408 // This is usually were we notice that a key has been composed and
2409 // thus failed to generate normal events.
2410 this.checkComposedKeys(event);
2412 // Some browsers don't report keypress events if ctrl or alt is pressed
2413 // for non-alphanumerical keys. Patch things up for now, but in the
2414 // future we will catch these keys earlier (in the keydown handler).
2415 if (this.lastNormalKeyDownEvent) {
2416 this.catchModifiersEarly = true;
2418 event.keyCode == 32 ||
2419 event.keyCode >= 48 && event.keyCode <= 57 ||
2420 event.keyCode >= 65 && event.keyCode <= 90;
2423 event.keyCode >= 96 && event.keyCode <= 105;
2426 event.keyCode == 59 || event.keyCode == 61 ||
2427 event.keyCode == 106 || event.keyCode == 107 ||
2428 event.keyCode >= 109 && event.keyCode <= 111 ||
2429 event.keyCode >= 186 && event.keyCode <= 192 ||
2430 event.keyCode >= 219 && event.keyCode <= 222 ||
2431 event.keyCode == 252;
2433 fake.ctrlKey = event.ctrlKey;
2434 fake.shiftKey = event.shiftKey;
2435 fake.altKey = event.altKey;
2436 fake.metaKey = event.metaKey;
2438 fake.charCode = event.keyCode;
2442 fake.keyCode = event.keyCode;
2443 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2444 fake = this.fixEvent(fake);
2447 this.lastNormalKeyDownEvent = undefined;
2448 this.handleKey(fake);
2454 event.cancelBubble = true;
2455 event.returnValue = false;
2460 this.lastKeyDownEvent = undefined;
2461 this.lastKeyPressedEvent = undefined;
2465 VT100.prototype.animateCursor = function(inactive) {
2466 if (!this.cursorInterval) {
2467 this.cursorInterval = setInterval(
2470 vt100.animateCursor();
2472 // Use this opportunity to check whether the user entered a composed
2473 // key, or whether somebody pasted text into the textfield.
2474 vt100.checkComposedKeys();
2478 if (inactive != undefined || this.cursor.className != 'inactive') {
2480 this.cursor.className = 'inactive';
2482 this.cursor.className = this.cursor.className == 'bright'
2488 VT100.prototype.blurCursor = function() {
2489 this.animateCursor(true);
2492 VT100.prototype.focusCursor = function() {
2493 this.animateCursor(false);
2496 VT100.prototype.flashScreen = function() {
2497 this.isInverted = !this.isInverted;
2498 this.refreshInvertedState();
2499 this.isInverted = !this.isInverted;
2500 setTimeout(function(vt100) {
2502 vt100.refreshInvertedState();
2507 VT100.prototype.beep = function() {
2508 if (this.visualBell) {
2515 this.beeper.src = 'beep.wav';
2522 VT100.prototype.bs = function() {
2523 if (this.cursorX > 0) {
2524 this.gotoXY(this.cursorX - 1, this.cursorY);
2525 this.needWrap = false;
2529 VT100.prototype.ht = function(count) {
2530 if (count == undefined) {
2533 var cx = this.cursorX;
2534 while (count-- > 0) {
2535 while (cx++ < this.terminalWidth) {
2536 var tabState = this.userTabStop[cx];
2537 if (tabState == false) {
2538 // Explicitly cleared tab stop
2540 } else if (tabState) {
2541 // Explicitly set tab stop
2544 // Default tab stop at each eighth column
2551 if (cx > this.terminalWidth - 1) {
2552 cx = this.terminalWidth - 1;
2554 if (cx != this.cursorX) {
2555 this.gotoXY(cx, this.cursorY);
2559 VT100.prototype.rt = function(count) {
2560 if (count == undefined) {
2563 var cx = this.cursorX;
2564 while (count-- > 0) {
2566 var tabState = this.userTabStop[cx];
2567 if (tabState == false) {
2568 // Explicitly cleared tab stop
2570 } else if (tabState) {
2571 // Explicitly set tab stop
2574 // Default tab stop at each eighth column
2584 if (cx != this.cursorX) {
2585 this.gotoXY(cx, this.cursorY);
2589 VT100.prototype.cr = function() {
2590 this.gotoXY(0, this.cursorY);
2591 this.needWrap = false;
2594 VT100.prototype.lf = function(count) {
2595 if (count == undefined) {
2598 if (count > this.terminalHeight) {
2599 count = this.terminalHeight;
2605 while (count-- > 0) {
2606 if (this.cursorY == this.bottom - 1) {
2607 this.scrollRegion(0, this.top + 1,
2608 this.terminalWidth, this.bottom - this.top - 1,
2609 0, -1, this.color, this.style);
2611 } else if (this.cursorY < this.terminalHeight - 1) {
2612 this.gotoXY(this.cursorX, this.cursorY + 1);
2617 VT100.prototype.ri = function(count) {
2618 if (count == undefined) {
2621 if (count > this.terminalHeight) {
2622 count = this.terminalHeight;
2628 while (count-- > 0) {
2629 if (this.cursorY == this.top) {
2630 this.scrollRegion(0, this.top,
2631 this.terminalWidth, this.bottom - this.top - 1,
2632 0, 1, this.color, this.style);
2633 } else if (this.cursorY > 0) {
2634 this.gotoXY(this.cursorX, this.cursorY - 1);
2637 this.needWrap = false;
2640 VT100.prototype.respondID = function() {
2641 this.respondString += '\u001B[?6c';
2644 VT100.prototype.respondSecondaryDA = function() {
2645 this.respondString += '\u001B[>0;0;0c';
2649 VT100.prototype.updateStyle = function() {
2651 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2652 this.style = 'text-decoration:underline;';
2654 var bg = (this.attr >> 4) & 0xF;
2655 var fg = this.attr & 0xF;
2656 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2661 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2662 fg = 8; // Dark grey
2663 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2666 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2669 // Make some readability enhancements. Most notably, disallow identical
2670 // background and foreground colors.
2672 if ((fg ^= 8) == 7) {
2676 // And disallow bright colors on a light-grey background.
2677 if (bg == 7 && fg >= 8) {
2678 if ((fg -= 8) == 7) {
2683 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2686 VT100.prototype.setAttrColors = function(attr) {
2687 if (attr != this.attr) {
2693 VT100.prototype.saveCursor = function() {
2694 this.savedX[this.currentScreen] = this.cursorX;
2695 this.savedY[this.currentScreen] = this.cursorY;
2696 this.savedAttr[this.currentScreen] = this.attr;
2697 this.savedUseGMap = this.useGMap;
2698 for (var i = 0; i < 4; i++) {
2699 this.savedGMap[i] = this.GMap[i];
2701 this.savedValid[this.currentScreen] = true;
2704 VT100.prototype.restoreCursor = function() {
2705 if (!this.savedValid[this.currentScreen]) {
2708 this.attr = this.savedAttr[this.currentScreen];
2710 this.useGMap = this.savedUseGMap;
2711 for (var i = 0; i < 4; i++) {
2712 this.GMap[i] = this.savedGMap[i];
2714 this.translate = this.GMap[this.useGMap];
2715 this.needWrap = false;
2716 this.gotoXY(this.savedX[this.currentScreen],
2717 this.savedY[this.currentScreen]);
2720 VT100.prototype.setMode = function(state) {
2721 for (var i = 0; i <= this.npar; i++) {
2722 if (this.isQuestionMark) {
2723 switch (this.par[i]) {
2724 case 1: this.cursorKeyMode = state; break;
2725 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2726 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2727 case 6: this.offsetMode = state; break;
2728 case 7: this.autoWrapMode = state; break;
2730 case 9: this.mouseReporting = state; break;
2731 case 25: this.cursorNeedsShowing = state;
2732 if (state) { this.showCursor(); }
2733 else { this.hideCursor(); } break;
2736 case 47: this.enableAlternateScreen(state); break;
2740 switch (this.par[i]) {
2741 case 3: this.dispCtrl = state; break;
2742 case 4: this.insertMode = state; break;
2743 case 20:this.crLfMode = state; break;
2750 VT100.prototype.statusReport = function() {
2751 // Ready and operational.
2752 this.respondString += '\u001B[0n';
2755 VT100.prototype.cursorReport = function() {
2756 this.respondString += '\u001B[' +
2757 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2759 (this.cursorX + 1) +
2763 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2764 // Changing of cursor color is not implemented.
2767 VT100.prototype.csiAt = function(number) {
2772 if (number > this.terminalWidth - this.cursorX) {
2773 number = this.terminalWidth - this.cursorX;
2775 this.scrollRegion(this.cursorX, this.cursorY,
2776 this.terminalWidth - this.cursorX - number, 1,
2777 number, 0, this.color, this.style);
2778 this.needWrap = false;
2781 VT100.prototype.csiJ = function(number) {
2783 case 0: // Erase from cursor to end of display
2784 this.clearRegion(this.cursorX, this.cursorY,
2785 this.terminalWidth - this.cursorX, 1,
2786 this.color, this.style);
2787 if (this.cursorY < this.terminalHeight-2) {
2788 this.clearRegion(0, this.cursorY+1,
2789 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2790 this.color, this.style);
2793 case 1: // Erase from start to cursor
2794 if (this.cursorY > 0) {
2795 this.clearRegion(0, 0,
2796 this.terminalWidth, this.cursorY,
2797 this.color, this.style);
2799 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
2800 this.color, this.style);
2802 case 2: // Erase whole display
2803 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
2804 this.color, this.style);
2812 VT100.prototype.csiK = function(number) {
2814 case 0: // Erase from cursor to end of line
2815 this.clearRegion(this.cursorX, this.cursorY,
2816 this.terminalWidth - this.cursorX, 1,
2817 this.color, this.style);
2819 case 1: // Erase from start of line to cursor
2820 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
2821 this.color, this.style);
2823 case 2: // Erase whole line
2824 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
2825 this.color, this.style);
2833 VT100.prototype.csiL = function(number) {
2834 // Open line by inserting blank line(s)
2835 if (this.cursorY >= this.bottom) {
2841 if (number > this.bottom - this.cursorY) {
2842 number = this.bottom - this.cursorY;
2844 this.scrollRegion(0, this.cursorY,
2845 this.terminalWidth, this.bottom - this.cursorY - number,
2846 0, number, this.color, this.style);
2850 VT100.prototype.csiM = function(number) {
2851 // Delete line(s), scrolling up the bottom of the screen.
2852 if (this.cursorY >= this.bottom) {
2858 if (number > this.bottom - this.cursorY) {
2859 number = bottom - cursorY;
2861 this.scrollRegion(0, this.cursorY + number,
2862 this.terminalWidth, this.bottom - this.cursorY - number,
2863 0, -number, this.color, this.style);
2867 VT100.prototype.csim = function() {
2868 for (var i = 0; i <= this.npar; i++) {
2869 switch (this.par[i]) {
2870 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2871 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2872 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2873 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2874 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2875 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
2877 this.translate = this.GMap[this.useGMap];
2878 this.dispCtrl = false;
2879 this.toggleMeta = false;
2882 this.translate = this.CodePage437Map;
2883 this.dispCtrl = true;
2884 this.toggleMeta = false;
2887 this.translate = this.CodePage437Map;
2888 this.dispCtrl = true;
2889 this.toggleMeta = true;
2892 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2893 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2894 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2895 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2896 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2897 0x0200 /* ATTR_UNDERLINE */; break;
2898 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
2899 case 49: this.attr |= 0xF0; break;
2901 if (this.par[i] >= 30 && this.par[i] <= 37) {
2902 var fg = this.par[i] - 30;
2903 this.attr = (this.attr & ~0x0F) | fg;
2904 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2905 var bg = this.par[i] - 40;
2906 this.attr = (this.attr & ~0xF0) | (bg << 4);
2914 VT100.prototype.csiP = function(number) {
2915 // Delete character(s) following cursor
2919 if (number > this.terminalWidth - this.cursorX) {
2920 number = this.terminalWidth - this.cursorX;
2922 this.scrollRegion(this.cursorX + number, this.cursorY,
2923 this.terminalWidth - this.cursorX - number, 1,
2924 -number, 0, this.color, this.style);
2928 VT100.prototype.csiX = function(number) {
2929 // Clear characters following cursor
2933 if (number > this.terminalWidth - this.cursorX) {
2934 number = this.terminalWidth - this.cursorX;
2936 this.clearRegion(this.cursorX, this.cursorY, number, 1,
2937 this.color, this.style);
2941 VT100.prototype.settermCommand = function() {
2942 // Setterm commands are not implemented
2945 VT100.prototype.doControl = function(ch) {
2948 case 0x00: /* ignored */ break;
2949 case 0x08: this.bs(); break;
2950 case 0x09: this.ht(); break;
2954 case 0x84: this.lf(); if (!this.crLfMode) break;
2955 case 0x0D: this.cr(); break;
2956 case 0x85: this.cr(); this.lf(); break;
2957 case 0x0E: this.useGMap = 1;
2958 this.translate = this.GMap[1];
2959 this.dispCtrl = true; break;
2960 case 0x0F: this.useGMap = 0;
2961 this.translate = this.GMap[0];
2962 this.dispCtrl = false; break;
2964 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
2965 case 0x1B: this.isEsc = 1 /* ESesc */; break;
2966 case 0x7F: /* ignored */ break;
2967 case 0x88: this.userTabStop[this.cursorX] = true; break;
2968 case 0x8D: this.ri(); break;
2969 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
2970 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
2971 case 0x9A: this.respondID(); break;
2972 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
2973 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
2977 default: switch (this.isEsc) {
2979 this.isEsc = 0 /* ESnormal */;
2981 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
2982 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
2984 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
2986 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
2988 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
2989 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
2990 /*7*/ case 0x37: this.saveCursor(); break;
2991 /*8*/ case 0x38: this.restoreCursor(); break;
2992 /*>*/ case 0x3E: this.applKeyMode = false; break;
2993 /*=*/ case 0x3D: this.applKeyMode = true; break;
2994 /*D*/ case 0x44: this.lf(); break;
2995 /*E*/ case 0x45: this.cr(); this.lf(); break;
2996 /*M*/ case 0x4D: this.ri(); break;
2997 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
2998 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
2999 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3000 /*Z*/ case 0x5A: this.respondID(); break;
3001 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3002 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3003 /*c*/ case 0x63: this.reset(); break;
3004 /*g*/ case 0x67: this.flashScreen(); break;
3008 case 15 /* ESnonstd */:
3012 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3013 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3014 this.isEsc = 16 /* ESpalette */; break;
3015 /*R*/ case 0x52: // Palette support is not implemented
3016 this.isEsc = 0 /* ESnormal */; break;
3017 default: this.isEsc = 0 /* ESnormal */; break;
3020 case 16 /* ESpalette */:
3021 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3022 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3023 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3024 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3026 if (this.npar == 7) {
3027 // Palette support is not implemented
3028 this.isEsc = 0 /* ESnormal */;
3031 this.isEsc = 0 /* ESnormal */;
3034 case 2 /* ESsquare */:
3036 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3037 0, 0, 0, 0, 0, 0, 0, 0 ];
3038 this.isEsc = 3 /* ESgetpars */;
3039 /*[*/ if (ch == 0x5B) { // Function key
3040 this.isEsc = 6 /* ESfunckey */;
3043 /*?*/ this.isQuestionMark = ch == 0x3F;
3044 if (this.isQuestionMark) {
3049 case 5 /* ESdeviceattr */:
3050 case 3 /* ESgetpars */:
3051 /*;*/ if (ch == 0x3B) {
3054 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3055 var par = this.par[this.npar];
3056 if (par == undefined) {
3059 this.par[this.npar] = 10*par + (ch & 0xF);
3061 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3063 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3064 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3065 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3066 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3069 this.isEsc = 0 /* ESnormal */;
3072 this.isEsc = 4 /* ESgotpars */;
3075 case 4 /* ESgotpars */:
3076 this.isEsc = 0 /* ESnormal */;
3077 if (this.isQuestionMark) {
3079 /*h*/ case 0x68: this.setMode(true); break;
3080 /*l*/ case 0x6C: this.setMode(false); break;
3081 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3084 this.isQuestionMark = false;
3088 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3089 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3091 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3092 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3093 this.cursorY - (this.par[0] ? this.par[0] : 1));
3096 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3097 this.cursorY + (this.par[0] ? this.par[0] : 1));
3100 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3101 this.cursorY); break;
3102 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3103 this.cursorY); break;
3104 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3106 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3108 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3110 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3111 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3112 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3113 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3114 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3115 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3116 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3117 /*m*/ case 0x6D: this.csim(); break;
3118 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3119 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3120 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3121 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3122 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3123 /*g*/ case 0x67: if (this.par[0] == 0) {
3124 this.userTabStop[this.cursorX] = false;
3125 } else if (this.par[0] == 2 || this.par[0] == 3) {
3126 this.userTabStop = [ ];
3127 for (var i = 0; i < this.terminalWidth; i++) {
3128 this.userTabStop[i] = false;
3132 /*h*/ case 0x68: this.setMode(true); break;
3133 /*l*/ case 0x6C: this.setMode(false); break;
3134 /*n*/ case 0x6E: switch (this.par[0]) {
3135 case 5: this.statusReport(); break;
3136 case 6: this.cursorReport(); break;
3140 /*q*/ case 0x71: // LED control not implemented
3142 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3143 var b = this.par[1] ? this.par[1]
3144 : this.terminalHeight;
3145 if (t < b && b <= this.terminalHeight) {
3151 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3152 if (c > this.terminalWidth * this.terminalHeight) {
3153 c = this.terminalWidth * this.terminalHeight;
3156 lineBuf += this.lastCharacter;
3159 /*s*/ case 0x73: this.saveCursor(); break;
3160 /*u*/ case 0x75: this.restoreCursor(); break;
3161 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3162 /*]*/ case 0x5D: this.settermCommand(); break;
3166 case 12 /* ESbang */:
3170 this.isEsc = 0 /* ESnormal */;
3172 case 13 /* ESpercent */:
3173 this.isEsc = 0 /* ESnormal */;
3175 /*@*/ case 0x40: this.utfEnabled = false; break;
3177 /*8*/ case 0x38: this.utfEnabled = true; break;
3181 case 6 /* ESfunckey */:
3182 this.isEsc = 0 /* ESnormal */; break;
3183 case 7 /* EShash */:
3184 this.isEsc = 0 /* ESnormal */;
3185 /*8*/ if (ch == 0x38) {
3186 // Screen alignment test not implemented
3189 case 8 /* ESsetG0 */:
3190 case 9 /* ESsetG1 */:
3191 case 10 /* ESsetG2 */:
3192 case 11 /* ESsetG3 */:
3193 var g = this.isEsc - 8 /* ESsetG0 */;
3194 this.isEsc = 0 /* ESnormal */;
3196 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3198 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3199 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3200 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3203 if (this.useGMap == g) {
3204 this.translate = this.GMap[g];
3207 case 17 /* ESstatus */:
3209 if (this.statusString && this.statusString.charAt(0) == ';') {
3210 this.statusString = this.statusString.substr(1);
3213 window.status = this.statusString;
3216 this.isEsc = 0 /* ESnormal */;
3218 this.statusString += String.fromCharCode(ch);
3221 case 18 /* ESss2 */:
3222 case 19 /* ESss3 */:
3224 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3225 [this.toggleMeta ? (ch | 0x80) : ch];
3226 if ((ch & 0xFF00) == 0xF000) {
3228 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3229 this.isEsc = 0 /* ESnormal */; break;
3232 this.lastCharacter = String.fromCharCode(ch);
3233 lineBuf += this.lastCharacter;
3234 this.isEsc = 0 /* ESnormal */; break;
3236 this.isEsc = 0 /* ESnormal */; break;
3243 VT100.prototype.renderString = function(s, showCursor) {
3244 // We try to minimize the number of DOM operations by coalescing individual
3245 // characters into strings. This is a significant performance improvement.
3246 var incX = s.length;
3247 if (incX > this.terminalWidth - this.cursorX) {
3248 incX = this.terminalWidth - this.cursorX;
3252 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3255 // Minimize the number of calls to putString(), by avoiding a direct
3256 // call to this.showCursor()
3257 this.cursor.style.visibility = '';
3259 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3262 VT100.prototype.vt100 = function(s) {
3263 this.cursorNeedsShowing = this.hideCursor();
3264 this.respondString = '';
3266 for (var i = 0; i < s.length; i++) {
3267 var ch = s.charCodeAt(i);
3268 if (this.utfEnabled) {
3269 // Decode UTF8 encoded character
3271 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3272 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3273 if (--this.utfCount <= 0) {
3274 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3283 if ((ch & 0xE0) == 0xC0) {
3285 this.utfChar = ch & 0x1F;
3286 } else if ((ch & 0xF0) == 0xE0) {
3288 this.utfChar = ch & 0x0F;
3289 } else if ((ch & 0xF8) == 0xF0) {
3291 this.utfChar = ch & 0x07;
3292 } else if ((ch & 0xFC) == 0xF8) {
3294 this.utfChar = ch & 0x03;
3295 } else if ((ch & 0xFE) == 0xFC) {
3297 this.utfChar = ch & 0x01;
3307 var isNormalCharacter =
3308 (ch >= 32 && ch <= 127 || ch >= 160 ||
3309 this.utfEnabled && ch >= 128 ||
3310 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3311 (ch != 0x7F || this.dispCtrl);
3313 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3315 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3317 if ((ch & 0xFF00) == 0xF000) {
3319 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3322 if (this.needWrap || this.insertMode) {
3324 this.renderString(lineBuf);
3328 if (this.needWrap) {
3329 this.cr(); this.lf();
3331 if (this.insertMode) {
3332 this.scrollRegion(this.cursorX, this.cursorY,
3333 this.terminalWidth - this.cursorX - 1, 1,
3334 1, 0, this.color, this.style);
3336 this.lastCharacter = String.fromCharCode(ch);
3337 lineBuf += this.lastCharacter;
3338 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3339 this.needWrap = this.autoWrapMode;
3343 this.renderString(lineBuf);
3346 var expand = this.doControl(ch);
3347 if (expand.length) {
3348 var r = this.respondString;
3349 this.respondString= r + this.vt100(expand);
3354 this.renderString(lineBuf, this.cursorNeedsShowing);
3355 } else if (this.cursorNeedsShowing) {
3358 return this.respondString;
3361 VT100.prototype.Latin1Map = [
3362 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3363 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3364 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3365 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3366 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3367 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3368 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3369 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3370 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3371 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3372 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3373 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3374 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3375 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3376 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3377 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3378 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3379 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3380 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3381 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3382 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3383 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3384 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3385 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3386 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3387 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3388 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3389 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3390 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3391 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3392 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3393 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3396 VT100.prototype.VT100GraphicsMap = [
3397 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3398 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3399 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3400 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3401 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3402 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3403 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3404 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3405 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3406 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3407 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3408 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3409 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3410 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3411 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3412 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3413 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3414 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3415 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3416 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3417 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3418 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3419 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3420 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3421 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3422 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3423 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3424 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3425 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3426 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3427 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3428 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3431 VT100.prototype.CodePage437Map = [
3432 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3433 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3434 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3435 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3436 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3437 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3438 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3439 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3440 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3441 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3442 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3443 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3444 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3445 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3446 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3447 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3448 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3449 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3450 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3451 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3452 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3453 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3454 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3455 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3456 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3457 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3458 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3459 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3460 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3461 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3462 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3463 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3466 VT100.prototype.DirectToFontMap = [
3467 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3468 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3469 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3470 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3471 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3472 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3473 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3474 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3475 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3476 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3477 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3478 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3479 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3480 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3481 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3482 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3483 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3484 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3485 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3486 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3487 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3488 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3489 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3490 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3491 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3492 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3493 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3494 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3495 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3496 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3497 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3498 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3501 VT100.prototype.ctrlAction = [
3502 true, false, false, false, false, false, false, true,
3503 true, true, true, true, true, true, true, true,
3504 false, false, false, false, false, false, false, false,
3505 true, false, true, true, false, false, false, false
3508 VT100.prototype.ctrlAlways = [
3509 true, false, false, false, false, false, false, false,
3510 true, false, true, false, true, true, true, true,
3511 false, false, false, false, false, false, false, false,
3512 false, false, false, true, false, false, false, false