1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
69 // #define ESgetpars 3
70 // #define ESgotpars 4
71 // #define ESdeviceattr 5
72 // #define ESfunckey 6
79 // #define ESpercent 13
80 // #define ESignore 14
81 // #define ESnonstd 15
82 // #define ESpalette 16
83 // #define ESstatus 17
87 // #define ATTR_DEFAULT 0x00F0
88 // #define ATTR_REVERSE 0x0100
89 // #define ATTR_UNDERLINE 0x0200
90 // #define ATTR_DIM 0x0400
91 // #define ATTR_BRIGHT 0x0800
92 // #define ATTR_BLINK 0x1000
94 // #define MOUSE_DOWN 0
96 // #define MOUSE_CLICK 2
98 function VT100(container) {
99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
106 // Optionally allow username and passwords.
107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
110 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
115 '(?::[1-9][0-9]*)?' +
118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
125 // Hostnames must end with a well-known top-level domain or must be
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
146 '(?::[1-9][0-9]{0,4})?' +
149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
155 '[-_.+a-zA-Z0-9]+@' +
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
173 // Optional arguments
174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
176 this.initializeElements(container);
177 this.initializeAnsiColors();
178 this.maxScrollbackLines = 500;
181 this.isQuestionMark = false;
184 this.savedAttr = [ ];
185 this.savedUseGMap = 0;
186 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187 this.CodePage437Map, this.DirectToFontMap ];
188 this.savedValid = [ ];
189 this.respondString = '';
190 this.statusString = '';
191 this.internalClipboard = undefined;
195 VT100.prototype.reset = function(clearHistory) {
196 this.isEsc = 0 /* ESnormal */;
197 this.needWrap = false;
198 this.autoWrapMode = true;
199 this.dispCtrl = false;
200 this.toggleMeta = false;
201 this.insertMode = false;
202 this.applKeyMode = false;
203 this.cursorKeyMode = false;
204 this.crLfMode = false;
205 this.offsetMode = false;
206 this.mouseReporting = false;
207 this.utfEnabled = true;
208 this.visualBell = typeof suppressAllAudio !=
214 this.attr = 0x00F0 /* ATTR_DEFAULT */;
216 this.GMap = [ this.Latin1Map,
217 this.VT100GraphicsMap,
219 this.DirectToFontMap ];
220 this.translate = this.GMap[this.useGMap];
222 this.bottom = this.terminalHeight;
223 this.lastCharacter = ' ';
224 this.userTabStop = [ ];
227 for (var i = 0; i < 2; i++) {
228 while (this.console[i].firstChild) {
229 this.console[i].removeChild(this.console[i].firstChild);
234 this.enableAlternateScreen(false);
237 this.isInverted = false;
238 this.refreshInvertedState();
239 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.style);
242 VT100.prototype.initializeAnsiColors = function() {
243 var elem = document.createElement('pre');
244 this.container.appendChild(elem);
245 this.setTextContent(elem, ' ');
247 for (var i = 0; i < 16; i++) {
248 elem.id = 'ansi' + i;
249 this.ansi[i] = this.getCurrentComputedStyle(elem, 'backgroundColor');
251 this.container.removeChild(elem);
254 VT100.prototype.addListener = function(elem, event, listener) {
255 if (elem.addEventListener) {
256 elem.addEventListener(event, listener, false);
258 elem.attachEvent('on' + event, listener);
262 VT100.prototype.initializeElements = function(container) {
263 // If the necessary objects have not already been defined in the HTML
264 // page, create them now.
266 this.container = container;
267 } else if (!(this.container = document.getElementById('vt100'))) {
268 this.container = document.createElement('div');
269 this.container.id = 'vt100';
270 document.body.appendChild(this.container);
273 if (!this.getChildById(this.container, 'reconnect') ||
274 !this.getChildById(this.container, 'menu') ||
275 !this.getChildById(this.container, 'scrollable') ||
276 !this.getChildById(this.container, 'console') ||
277 !this.getChildById(this.container, 'alt_console') ||
278 !this.getChildById(this.container, 'ieprobe') ||
279 !this.getChildById(this.container, 'padding') ||
280 !this.getChildById(this.container, 'cursor') ||
281 !this.getChildById(this.container, 'lineheight') ||
282 !this.getChildById(this.container, 'space') ||
283 !this.getChildById(this.container, 'input') ||
284 !this.getChildById(this.container, 'cliphelper') ||
285 !this.getChildById(this.container, 'attrib')) {
286 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
287 // we might get a pointless warning that a suitable plugin is not yet
288 // installed. If in doubt, we'd rather just stay silent.
291 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
293 embed = typeof suppressAllAudio != 'undefined' &&
294 suppressAllAudio ? "" :
295 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
298 'autostart="false" ' +
300 'enablejavascript="true" ' +
301 'type="audio/x-wav" ' +
304 'style="position:absolute;left:-1000px;top:-1000px" />';
309 this.container.innerHTML =
310 '<div id="reconnect" style="visibility: hidden">' +
311 '<input type="button" value="Connect" ' +
312 'onsubmit="return false" />' +
314 '<div id="cursize" style="visibility: hidden">' +
316 '<div id="menu"></div>' +
317 '<div id="scrollable">' +
318 '<pre id="lineheight"> </pre>' +
319 '<pre id="console">' +
321 '<div id="ieprobe"><span> </span></div>' +
323 '<pre id="alt_console" style="display: none"></pre>' +
324 '<div id="padding"></div>' +
325 '<pre id="cursor"> </pre>' +
327 '<div class="hidden">' +
328 '<pre><div><span id="space"></span></div></pre>' +
329 '<input type="textfield" id="input" />' +
330 '<input type="textfield" id="cliphelper" />' +
331 '<span id="attrib"> </span>' +
332 (typeof suppressAllAudio != 'undefined' &&
333 suppressAllAudio ? "" :
334 embed + '<bgsound id="beep_bgsound" loop=1 />') +
338 // Find the object used for playing the "beep" sound, if any.
339 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
340 this.beeper = undefined;
342 this.beeper = this.getChildById(this.container,
344 if (!this.beeper || !this.beeper.Play) {
345 this.beeper = this.getChildById(this.container,
347 if (!this.beeper || typeof this.beeper.src == 'undefined') {
348 this.beeper = undefined;
353 // Initialize the variables for finding the text console and the
355 this.reconnectBtn = this.getChildById(this.container,'reconnect');
356 this.curSizeBox = this.getChildById(this.container, 'cursize');
357 this.menu = this.getChildById(this.container, 'menu');
358 this.scrollable = this.getChildById(this.container,
360 this.lineheight = this.getChildById(this.container,
363 [ this.getChildById(this.container, 'console'),
364 this.getChildById(this.container, 'alt_console') ];
365 var ieProbe = this.getChildById(this.container, 'ieprobe');
366 this.padding = this.getChildById(this.container, 'padding');
367 this.cursor = this.getChildById(this.container, 'cursor');
368 this.space = this.getChildById(this.container, 'space');
369 this.input = this.getChildById(this.container, 'input');
370 this.cliphelper = this.getChildById(this.container,
372 this.attributeHelper = this.getChildById(this.container, 'attrib');
374 // Remember the dimensions of a standard character glyph. We would
375 // expect that we could just check cursor.clientWidth/Height at any time,
376 // but it turns out that browsers sometimes invalidate these values
377 // (e.g. while displaying a print preview screen).
378 this.cursorWidth = this.cursor.clientWidth;
379 this.cursorHeight = this.lineheight.clientHeight;
381 // IE has a slightly different boxing model, that we need to compensate for
382 this.isIE = ieProbe.offsetTop > 1;
384 this.console.innerHTML = '';
386 // Determine if the terminal window is positioned at the beginning of the
387 // page, or if it is embedded somewhere else in the page. For full-screen
388 // terminals, automatically resize whenever the browser window changes.
389 var marginTop = parseInt(this.getCurrentComputedStyle(
390 document.body, 'marginTop'));
391 var marginLeft = parseInt(this.getCurrentComputedStyle(
392 document.body, 'marginLeft'));
393 var marginRight = parseInt(this.getCurrentComputedStyle(
394 document.body, 'marginRight'));
395 var x = this.container.offsetLeft;
396 var y = this.container.offsetTop;
397 for (var parent = this.container; parent = parent.offsetParent; ) {
398 x += parent.offsetLeft;
399 y += parent.offsetTop;
401 this.isEmbedded = marginTop != y ||
403 (window.innerWidth ||
404 document.documentElement.clientWidth ||
405 document.body.clientWidth) -
406 marginRight != x + this.container.offsetWidth;
407 if (!this.isEmbedded) {
408 // Some browsers generate resize events when the terminal is first
409 // shown. Disable showing the size indicator until a little bit after
410 // the terminal has been rendered the first time.
411 this.indicateSize = false;
412 setTimeout(function(vt100) {
414 vt100.indicateSize = true;
417 this.addListener(window, 'resize',
420 vt100.hideContextMenu();
422 vt100.showCurrentSize();
426 // Hide extra scrollbars attached to window
427 document.body.style.margin = '0px';
428 try { document.body.style.overflow ='hidden'; } catch (e) { }
429 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
433 this.hideContextMenu();
435 // Add listener to reconnect button
436 this.addListener(this.reconnectBtn.firstChild, 'click',
439 var rc = vt100.reconnect();
445 // Add input listeners
446 this.addListener(this.input, 'blur',
448 return function() { vt100.blurCursor(); } }(this));
449 this.addListener(this.input, 'focus',
451 return function() { vt100.focusCursor(); } }(this));
452 this.addListener(this.input, 'keydown',
455 if (!e) e = window.event;
456 return vt100.keyDown(e); } }(this));
457 this.addListener(this.input, 'keypress',
460 if (!e) e = window.event;
461 return vt100.keyPressed(e); } }(this));
462 this.addListener(this.input, 'keyup',
465 if (!e) e = window.event;
466 return vt100.keyUp(e); } }(this));
468 // Attach listeners that move the focus to the <input> field. This way we
469 // can make sure that we can receive keyboard input.
470 var mouseEvent = function(vt100, type) {
472 if (!e) e = window.event;
473 return vt100.mouseEvent(e, type);
476 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
477 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
478 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
480 // Initialize the blank terminal window.
481 this.currentScreen = 0;
484 this.numScrollbackLines = 0;
486 this.bottom = 0x7FFFFFFF;
492 VT100.prototype.getChildById = function(parent, id) {
493 var nodeList = parent.all || parent.getElementsByTagName('*');
494 if (typeof nodeList.namedItem == 'undefined') {
495 for (var i = 0; i < nodeList.length; i++) {
496 if (nodeList[i].id == id) {
502 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
503 return elem ? elem[0] || elem : null;
507 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
508 if (typeof elem.currentStyle != 'undefined') {
509 return elem.currentStyle[style];
511 return document.defaultView.getComputedStyle(elem, null)[style];
515 VT100.prototype.reconnect = function() {
519 VT100.prototype.showReconnect = function(state) {
521 this.reconnectBtn.style.visibility = '';
523 this.reconnectBtn.style.visibility = 'hidden';
527 VT100.prototype.repairElements = function(console) {
528 for (var line = console.firstChild; line; line = line.nextSibling) {
529 if (!line.clientHeight) {
530 var newLine = document.createElement(line.tagName);
531 newLine.style.cssText = line.style.cssText;
532 newLine.className = line.className;
533 if (line.tagName == 'DIV') {
534 for (var span = line.firstChild; span; span = span.nextSibling) {
535 var newSpan = document.createElement(span.tagName);
536 newSpan.style.cssText = span.style.cssText;
537 this.setTextContent(newSpan, this.getTextContent(span));
538 newLine.appendChild(newSpan);
541 this.setTextContent(newLine, this.getTextContent(line));
543 line.parentNode.replaceChild(newLine, line);
549 VT100.prototype.resized = function(w, h) {
552 VT100.prototype.resizer = function() {
553 // The cursor can get corrupted if the print-preview is displayed in Firefox.
554 // Recreating it, will repair it.
555 var newCursor = document.createElement('pre');
556 this.setTextContent(newCursor, ' ');
557 newCursor.id = 'cursor';
558 newCursor.style.cssText = this.cursor.style.cssText;
559 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
560 if (!newCursor.clientHeight) {
561 // Things are broken right now. This is probably because we are
562 // displaying the print-preview. Just don't change any of our settings
563 // until the print dialog is closed again.
564 newCursor.parentNode.removeChild(newCursor);
567 // Swap the old broken cursor for the newly created one.
568 this.cursor.parentNode.removeChild(this.cursor);
569 this.cursor = newCursor;
572 // Really horrible things happen if the contents of the terminal changes
573 // while the print-preview is showing. We get HTML elements that show up
574 // in the DOM, but that do not take up any space. Find these elements and
576 this.repairElements(this.console[0]);
577 this.repairElements(this.console[1]);
579 // Lock the cursor size to the size of a normal character. This helps with
580 // characters that are taller/shorter than normal. Unfortunately, we will
581 // still get confused if somebody enters a character that is wider/narrower
582 // than normal. This can happen if the browser tries to substitute a
583 // characters from a different font.
584 this.cursor.style.width = this.cursorWidth + 'px';
585 this.cursor.style.height = this.cursorHeight + 'px';
587 // Adjust height for one pixel padding of the #vt100 element.
588 // The latter is necessary to properly display the inactive cursor.
589 var console = this.console[this.currentScreen];
590 var height = (this.isEmbedded ? this.container.clientHeight
591 : (window.innerHeight ||
592 document.documentElement.clientHeight ||
593 document.body.clientHeight))-1;
594 var partial = height % this.cursorHeight;
595 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
596 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
597 var oldTerminalHeight = this.terminalHeight;
601 // Clip the cursor to the visible screen.
602 var cx = this.cursorX;
603 var cy = this.cursorY + this.numScrollbackLines;
605 // The alternate screen never keeps a scroll back buffer.
606 this.updateNumScrollbackLines();
607 while (this.currentScreen && this.numScrollbackLines > 0) {
608 console.removeChild(console.firstChild);
609 this.numScrollbackLines--;
611 cy -= this.numScrollbackLines;
614 } else if (cx > this.terminalWidth) {
615 cx = this.terminalWidth - 1;
622 } else if (cy > this.terminalHeight) {
623 cy = this.terminalHeight - 1;
629 // Clip the scroll region to the visible screen.
630 if (this.bottom > this.terminalHeight ||
631 this.bottom == oldTerminalHeight) {
632 this.bottom = this.terminalHeight;
634 if (this.top >= this.bottom) {
635 this.top = this.bottom-1;
641 // Truncate lines, if necessary. Explicitly reposition cursor (this is
642 // particularly important after changing the screen number), and reset
643 // the scroll region to the default.
644 this.truncateLines(this.terminalWidth);
645 this.putString(cx, cy, '', undefined);
646 this.scrollable.scrollTop = this.numScrollbackLines *
647 this.cursorHeight + 1;
649 // Update classNames for lines in the scrollback buffer
650 var line = console.firstChild;
651 for (var i = 0; i < this.numScrollbackLines; i++) {
652 line.className = 'scrollback';
653 line = line.nextSibling;
657 line = line.nextSibling;
660 // Reposition the reconnect button
661 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
662 this.reconnectBtn.clientWidth)/2 + 'px';
663 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
664 this.reconnectBtn.clientHeight)/2 + 'px';
666 // Send notification that the window size has been changed
667 this.resized(this.terminalWidth, this.terminalHeight);
670 VT100.prototype.showCurrentSize = function() {
671 if (!this.indicateSize) {
674 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
676 this.curSizeBox.style.left =
677 (this.terminalWidth*this.cursorWidth -
678 this.curSizeBox.clientWidth)/2 + 'px';
679 this.curSizeBox.style.top =
680 (this.terminalHeight*this.cursorHeight -
681 this.curSizeBox.clientHeight)/2 + 'px';
682 this.curSizeBox.style.visibility = '';
683 if (this.curSizeTimeout) {
684 clearTimeout(this.curSizeTimeout);
687 // Only show the terminal size for a short amount of time after resizing.
688 // Then hide this information, again. Some browsers generate resize events
689 // throughout the entire resize operation. This is nice, and we will show
690 // the terminal size while the user is dragging the window borders.
691 // Other browsers only generate a single event when the user releases the
692 // mouse. In those cases, we can only show the terminal size once at the
693 // end of the resize operation.
694 this.curSizeTimeout = setTimeout(function(vt100) {
696 vt100.curSizeTimeout = null;
697 vt100.curSizeBox.style.visibility = 'hidden';
702 VT100.prototype.selection = function() {
704 return '' + (window.getSelection && window.getSelection() ||
705 document.selection && document.selection.type == 'Text' &&
706 document.selection.createRange().text || '');
712 VT100.prototype.cancelEvent = function(event) {
714 // For non-IE browsers
715 event.stopPropagation();
716 event.preventDefault();
721 event.cancelBubble = true;
722 event.returnValue = false;
730 VT100.prototype.mouseEvent = function(event, type) {
731 // If any text is currently selected, do not move the focus as that would
732 // invalidate the selection.
733 var selection = this.selection();
734 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
738 // Compute mouse position in characters.
739 var offsetX = this.container.offsetLeft;
740 var offsetY = this.container.offsetTop;
741 for (var e = this.container; e = e.offsetParent; ) {
742 offsetX += e.offsetLeft;
743 offsetY += e.offsetTop;
745 var x = (event.clientX - offsetX) / this.cursorWidth;
746 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
747 this.cursorHeight - this.numScrollbackLines;
749 if (x >= this.terminalWidth) {
750 x = this.terminalWidth - 1;
757 if (y >= this.terminalHeight) {
758 y = this.terminalHeight - 1;
766 // Compute button number and modifier keys.
767 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
768 typeof event.pageX != 'undefined' ? event.button :
769 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
770 if (button != undefined) {
771 if (event.shiftKey) {
774 if (event.altKey || event.metaKey) {
782 // Report mouse events if they happen inside of the current screen and
783 // with the SHIFT key unpressed. Both of these restrictions do not apply
784 // for button releases, as we always want to report those.
785 if (this.mouseReporting && !selection.length &&
786 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
787 if (inside || type != 0 /* MOUSE_DOWN */) {
788 if (button != undefined) {
789 var report = '\u001B[M' + String.fromCharCode(button + 32) +
790 String.fromCharCode(x + 33) +
791 String.fromCharCode(y + 33);
792 if (type != 2 /* MOUSE_CLICK */) {
793 this.keysPressed(report);
796 // If we reported the event, stop propagating it (not sure, if this
797 // actually works on most browsers; blocking the global "oncontextmenu"
798 // even is still necessary).
799 return this.cancelEvent(event);
804 // Bring up context menu.
805 if (button == 2 && !event.shiftKey) {
806 if (type == 0 /* MOUSE_DOWN */) {
807 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
809 return this.cancelEvent(event);
812 if (this.mouseReporting) {
814 event.shiftKey = false;
822 VT100.prototype.replaceChar = function(s, ch, repl) {
824 i = s.indexOf(ch, i + 1);
828 s = s.substr(0, i) + repl + s.substr(i + 1);
833 VT100.prototype.htmlEscape = function(s) {
834 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
835 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
838 VT100.prototype.getTextContent = function(elem) {
839 return elem.textContent ||
840 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
843 VT100.prototype.setTextContent = function(elem, s) {
844 // Check if we find any URLs in the text. If so, automatically convert them
846 if (this.urlRE && this.urlRE.test(s)) {
850 if (RegExp.leftContext != null) {
851 inner += this.htmlEscape(RegExp.leftContext);
852 consumed += RegExp.leftContext.length;
854 var url = this.htmlEscape(RegExp.lastMatch);
857 // If no protocol was specified, try to guess a reasonable one.
858 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
859 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
860 var slash = url.indexOf('/');
861 var at = url.indexOf('@');
862 var question = url.indexOf('?');
864 (at < question || question < 0) &&
865 (slash < 0 || (question > 0 && slash > question))) {
866 fullUrl = 'mailto:' + url;
868 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
873 inner += '<a target="vt100Link" href="' + fullUrl +
875 consumed += RegExp.lastMatch.length;
876 s = s.substr(consumed);
877 if (!this.urlRE.test(s)) {
878 if (RegExp.rightContext != null) {
879 inner += this.htmlEscape(RegExp.rightContext);
884 elem.innerHTML = inner;
888 // Updating the content of an element is an expensive operation. It actually
889 // pays off to first check whether the element is still unchanged.
890 if (typeof elem.textContent == 'undefined') {
891 if (elem.innerText != s) {
895 // Very old versions of IE do not allow setting innerText. Instead,
896 // remove all children, by setting innerHTML and then set the text
897 // using DOM methods.
899 elem.appendChild(document.createTextNode(
900 this.replaceChar(s, ' ', '\u00A0')));
904 if (elem.textContent != s) {
905 elem.textContent = s;
910 VT100.prototype.insertBlankLine = function(y, style) {
911 // Insert a blank line a position y. This method ignores the scrollback
912 // buffer. The caller has to add the length of the scrollback buffer to
913 // the position, if necessary.
914 // If the position is larger than the number of current lines, this
915 // method just adds a new line right after the last existing one. It does
916 // not add any missing lines in between. It is the caller's responsibility
918 if (style == undefined) {
923 line = document.createElement('pre');
924 this.setTextContent(line, '\n');
926 line = document.createElement('div');
927 var span = document.createElement('span');
928 span.style.cssText = style;
929 this.setTextContent(span, this.spaces(this.terminalWidth));
930 line.appendChild(span);
932 line.style.height = this.cursorHeight + 'px';
933 var console = this.console[this.currentScreen];
934 if (console.childNodes.length > y) {
935 console.insertBefore(line, console.childNodes[y]);
937 console.appendChild(line);
941 VT100.prototype.updateWidth = function() {
942 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
944 return this.terminalWidth;
947 VT100.prototype.updateHeight = function() {
948 // We want to be able to display either a terminal window that fills the
949 // entire browser window, or a terminal window that is contained in a
950 // <div> which is embededded somewhere in the web page.
951 if (this.isEmbedded) {
952 // Embedded terminal. Use size of the containing <div> (id="vt100").
953 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
956 // Use the full browser window.
957 this.terminalHeight = Math.floor(((window.innerHeight ||
958 document.documentElement.clientHeight ||
959 document.body.clientHeight)-1)/
962 return this.terminalHeight;
965 VT100.prototype.updateNumScrollbackLines = function() {
966 var scrollback = Math.floor(
967 this.console[this.currentScreen].offsetHeight /
970 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
971 return this.numScrollbackLines;
974 VT100.prototype.truncateLines = function(width) {
978 for (var line = this.console[this.currentScreen].firstChild; line;
979 line = line.nextSibling) {
980 if (line.tagName == 'DIV') {
983 // Traverse current line and truncate it once we saw "width" characters
984 for (var span = line.firstChild; span;
985 span = span.nextSibling) {
986 var s = this.getTextContent(span);
989 this.setTextContent(span, s.substr(0, width - x));
990 while (span.nextSibling) {
991 line.removeChild(line.lastChild);
997 // Prune white space from the end of the current line
998 var span = line.lastChild;
999 while (span && !span.style.cssText.length) {
1000 // Scan backwards looking for first non-space character
1001 var s = this.getTextContent(span);
1002 for (var i = s.length; i--; ) {
1003 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1004 if (i+1 != s.length) {
1005 this.setTextContent(s.substr(0, i+1));
1013 span = span.previousSibling;
1015 // Remove blank <span>'s from end of line
1016 line.removeChild(sibling);
1018 // Remove entire line (i.e. <div>), if empty
1019 var blank = document.createElement('pre');
1020 blank.style.height = this.cursorHeight + 'px';
1021 this.setTextContent(blank, '\n');
1022 line.parentNode.replaceChild(blank, line);
1030 VT100.prototype.putString = function(x, y, text, style) {
1034 var yIdx = y + this.numScrollbackLines;
1040 var console = this.console[this.currentScreen];
1041 if (!text.length && (yIdx >= console.childNodes.length ||
1042 console.childNodes[yIdx].tagName != 'DIV')) {
1043 // Positioning cursor to a blank location
1046 // Create missing blank lines at end of page
1047 while (console.childNodes.length <= yIdx) {
1048 // In order to simplify lookups, we want to make sure that each line
1049 // is represented by exactly one element (and possibly a whole bunch of
1051 // For non-blank lines, we can create a <div> containing one or more
1052 // <span>s. For blank lines, this fails as browsers tend to optimize them
1053 // away. But fortunately, a <pre> tag containing a newline character
1054 // appears to work for all browsers (a would also work, but then
1055 // copying from the browser window would insert superfluous spaces into
1057 this.insertBlankLine(yIdx);
1059 line = console.childNodes[yIdx];
1061 // If necessary, promote blank '\n' line to a <div> tag
1062 if (line.tagName != 'DIV') {
1063 var div = document.createElement('div');
1064 div.style.height = this.cursorHeight + 'px';
1065 div.innerHTML = '<span></span>';
1066 console.replaceChild(div, line);
1070 // Scan through list of <span>'s until we find the one where our text
1072 span = line.firstChild;
1074 while (span.nextSibling && xPos < x) {
1075 len = this.getTextContent(span).length;
1076 if (xPos + len > x) {
1080 span = span.nextSibling;
1084 // If current <span> is not long enough, pad with spaces or add new
1086 s = this.getTextContent(span);
1087 var oldStyle = span.style.cssText;
1088 if (xPos + s.length < x) {
1089 if (oldStyle != '') {
1090 span = document.createElement('span');
1091 line.appendChild(span);
1092 span.style.cssText = '';
1099 } while (xPos + s.length < x);
1102 // If styles do not match, create a new <span>
1103 var del = text.length - s.length + x - xPos;
1104 if (oldStyle != style && (oldStyle || style)) {
1106 // Replacing text at beginning of existing <span>
1107 if (text.length >= s.length) {
1108 // New text is equal or longer than existing text
1111 // Insert new <span> before the current one, then remove leading
1112 // part of existing <span>, adjust style of new <span>, and finally
1114 sibling = document.createElement('span');
1115 line.insertBefore(sibling, span);
1116 this.setTextContent(span, s.substr(text.length));
1121 // Replacing text some way into the existing <span>
1122 var remainder = s.substr(x + text.length - xPos);
1123 this.setTextContent(span, s.substr(0, x - xPos));
1125 sibling = document.createElement('span');
1126 if (span.nextSibling) {
1127 line.insertBefore(sibling, span.nextSibling);
1129 if (remainder.length) {
1130 sibling = document.createElement('span');
1131 sibling.style.cssText = oldStyle;
1132 this.setTextContent(sibling, remainder);
1133 line.insertBefore(sibling, span.nextSibling);
1136 line.appendChild(sibling);
1138 if (remainder.length) {
1139 sibling = document.createElement('span');
1140 sibling.style.cssText = oldStyle;
1141 this.setTextContent(sibling, remainder);
1142 line.appendChild(sibling);
1147 span.style.cssText = style;
1149 // Overwrite (partial) <span> with new text
1150 s = s.substr(0, x - xPos) +
1152 s.substr(x + text.length - xPos);
1154 this.setTextContent(span, s);
1157 // Delete all subsequent <span>'s that have just been overwritten
1158 sibling = span.nextSibling;
1159 while (del > 0 && sibling) {
1160 s = this.getTextContent(sibling);
1163 line.removeChild(sibling);
1165 sibling = span.nextSibling;
1167 this.setTextContent(sibling, s.substr(del));
1172 // Merge <span> with next sibling, if styles are identical
1173 if (sibling && span.style.cssText == sibling.style.cssText) {
1174 this.setTextContent(span,
1175 this.getTextContent(span) +
1176 this.getTextContent(sibling));
1177 line.removeChild(sibling);
1183 this.cursorX = x + text.length;
1184 if (this.cursorX >= this.terminalWidth) {
1185 this.cursorX = this.terminalWidth - 1;
1186 if (this.cursorX < 0) {
1192 if (!this.cursor.style.visibility) {
1193 var idx = this.cursorX - xPos;
1195 // If we are in a non-empty line, take the cursor Y position from the
1196 // other elements in this line. If dealing with broken, non-proportional
1197 // fonts, this is likely to yield better results.
1198 pixelY = span.offsetTop +
1199 span.offsetParent.offsetTop;
1200 s = this.getTextContent(span);
1201 var nxtIdx = idx - s.length;
1203 this.setTextContent(this.cursor, s.charAt(idx));
1204 pixelX = span.offsetLeft +
1205 idx*span.offsetWidth / s.length;
1208 pixelX = span.offsetLeft + span.offsetWidth;
1210 if (span.nextSibling) {
1211 s = this.getTextContent(span.nextSibling);
1212 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1214 pixelX = span.nextSibling.offsetLeft +
1215 nxtIdx*span.offsetWidth / s.length;
1218 this.setTextContent(this.cursor, ' ');
1222 this.setTextContent(this.cursor, ' ');
1226 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
1228 this.setTextContent(this.space, this.spaces(this.cursorX));
1229 this.cursor.style.left = this.space.offsetWidth +
1230 console.offsetLeft + 'px';
1232 this.cursorY = yIdx - this.numScrollbackLines;
1234 this.cursor.style.top = pixelY + 'px';
1236 this.cursor.style.top = yIdx*this.cursorHeight +
1237 console.offsetTop + 'px';
1241 // Merge <span> with previous sibling, if styles are identical
1242 if ((sibling = span.previousSibling) &&
1243 span.style.cssText == sibling.style.cssText) {
1244 this.setTextContent(span,
1245 this.getTextContent(sibling) +
1246 this.getTextContent(span));
1247 line.removeChild(sibling);
1250 // Prune white space from the end of the current line
1251 span = line.lastChild;
1252 while (span && !span.style.cssText.length) {
1253 // Scan backwards looking for first non-space character
1254 s = this.getTextContent(span);
1255 for (var i = s.length; i--; ) {
1256 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1257 if (i+1 != s.length) {
1258 this.setTextContent(s.substr(0, i+1));
1266 span = span.previousSibling;
1268 // Remove blank <span>'s from end of line
1269 line.removeChild(sibling);
1271 // Remove entire line (i.e. <div>), if empty
1272 var blank = document.createElement('pre');
1273 blank.style.height = this.cursorHeight + 'px';
1274 this.setTextContent(blank, '\n');
1275 line.parentNode.replaceChild(blank, line);
1282 VT100.prototype.gotoXY = function(x, y) {
1283 if (x >= this.terminalWidth) {
1284 x = this.terminalWidth - 1;
1290 if (this.offsetMode) {
1295 maxY = this.terminalHeight;
1303 this.putString(x, y, '', undefined);
1304 this.needWrap = false;
1307 VT100.prototype.gotoXaY = function(x, y) {
1308 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1311 VT100.prototype.refreshInvertedState = function() {
1312 if (this.isInverted) {
1313 this.scrollable.style.color = this.ansi[15];
1314 this.scrollable.style.backgroundColor = this.ansi[0];
1316 this.scrollable.style.color = '';
1317 this.scrollable.style.backgroundColor = '';
1321 VT100.prototype.enableAlternateScreen = function(state) {
1322 // Don't do anything, if we are already on the desired screen
1323 if ((state ? 1 : 0) == this.currentScreen) {
1324 // Calling the resizer is not actually necessary. But it is a good way
1325 // of resetting state that might have gotten corrupted.
1330 // We save the full state of the normal screen, when we switch away from it.
1331 // But for the alternate screen, no saving is necessary. We always reset
1332 // it when we switch to it.
1337 // Display new screen, and initialize state (the resizer does that for us).
1338 this.currentScreen = state ? 1 : 0;
1339 this.console[1-this.currentScreen].style.display = 'none';
1340 this.console[this.currentScreen].style.display = '';
1343 // If we switched to the alternate screen, reset it completely. Otherwise,
1344 // restore the saved state.
1347 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1349 this.restoreCursor();
1353 VT100.prototype.hideCursor = function() {
1354 var hidden = this.cursor.style.visibility == 'hidden';
1356 this.cursor.style.visibility = 'hidden';
1362 VT100.prototype.showCursor = function(x, y) {
1363 if (this.cursor.style.visibility) {
1364 this.cursor.style.visibility = '';
1365 this.putString(x == undefined ? this.cursorX : x,
1366 y == undefined ? this.cursorY : y,
1373 VT100.prototype.scrollBack = function() {
1374 var i = this.scrollable.scrollTop -
1375 this.scrollable.clientHeight;
1376 this.scrollable.scrollTop = i < 0 ? 0 : i;
1379 VT100.prototype.scrollFore = function() {
1380 var i = this.scrollable.scrollTop +
1381 this.scrollable.clientHeight;
1382 this.scrollable.scrollTop = i > this.numScrollbackLines *
1383 this.cursorHeight + 1
1384 ? this.numScrollbackLines *
1385 this.cursorHeight + 1
1389 VT100.prototype.spaces = function(i) {
1397 VT100.prototype.clearRegion = function(x, y, w, h, style) {
1402 if (w > this.terminalWidth) {
1403 w = this.terminalWidth;
1405 if ((w -= x) <= 0) {
1412 if (h > this.terminalHeight) {
1413 h = this.terminalHeight;
1415 if ((h -= y) <= 0) {
1419 // Special case the situation where we clear the entire screen, and we do
1420 // not have a scrollback buffer. In that case, we should just remove all
1422 if (!this.numScrollbackLines &&
1423 w == this.terminalWidth && h == this.terminalHeight &&
1425 var console = this.console[this.currentScreen];
1426 while (console.lastChild) {
1427 console.removeChild(console.lastChild);
1429 this.putString(this.cursorX, this.cursorY, '', undefined);
1431 var hidden = this.hideCursor();
1432 var cx = this.cursorX;
1433 var cy = this.cursorY;
1434 var s = this.spaces(w);
1435 for (var i = y+h; i-- > y; ) {
1436 this.putString(x, i, s, style);
1438 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1442 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1445 var console = this.console[this.currentScreen];
1446 if (sY >= console.childNodes.length) {
1447 text[0] = this.spaces(w);
1450 var line = console.childNodes[sY];
1451 if (line.tagName != 'DIV' || !line.childNodes.length) {
1452 text[0] = this.spaces(w);
1456 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1457 var s = this.getTextContent(span);
1460 var o = sX > x ? sX - x : 0;
1461 text[text.length] = s.substr(o, w);
1462 style[style.length] = span.style.cssText;
1468 text[text.length] = this.spaces(w);
1469 style[style.length] = null;
1473 var hidden = this.hideCursor();
1474 var cx = this.cursorX;
1475 var cy = this.cursorY;
1476 for (var i = 0; i < text.length; i++) {
1477 this.putString(dX, dY - this.numScrollbackLines, text[i], style[i]);
1478 dX += text[i].length;
1480 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1483 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY, style) {
1484 var left = incX < 0 ? -incX : 0;
1485 var right = incX > 0 ? incX : 0;
1486 var up = incY < 0 ? -incY : 0;
1487 var down = incY > 0 ? incY : 0;
1489 // Clip region against terminal size
1490 var dontScroll = null;
1495 if (w > this.terminalWidth - right) {
1496 w = this.terminalWidth - right;
1498 if ((w -= x) <= 0) {
1505 if (h > this.terminalHeight - down) {
1506 h = this.terminalHeight - down;
1512 if (style && style.indexOf('underline')) {
1513 // Different terminal emulators disagree on the attributes that
1514 // are used for scrolling. The consensus seems to be, never to
1515 // fill with underlined spaces. N.B. this is different from the
1516 // cases when the user blanks a region. User-initiated blanking
1517 // always fills with all of the current attributes.
1518 this.attributeHelper.cssText
1519 = style.replace(/text-decoration:underline;/, "");
1520 style = this.attributeHelper.cssText;
1523 // Compute current scroll position
1524 var scrollPos = this.numScrollbackLines -
1525 (this.scrollable.scrollTop-1) / this.cursorHeight;
1527 // Determine original cursor position. Hide cursor temporarily to avoid
1528 // visual artifacts.
1529 var hidden = this.hideCursor();
1530 var cx = this.cursorX;
1531 var cy = this.cursorY;
1532 var console = this.console[this.currentScreen];
1534 if (!incX && !x && w == this.terminalWidth) {
1535 // Scrolling entire lines
1538 if (!this.currentScreen && y == -incY &&
1539 h == this.terminalHeight + incY) {
1540 // Scrolling up with adding to the scrollback buffer. This is only
1541 // possible if there are at least as many lines in the console,
1542 // as the terminal is high
1543 while (console.childNodes.length < this.terminalHeight) {
1544 this.insertBlankLine(this.terminalHeight);
1547 // Add new lines at bottom in order to force scrolling
1548 for (var i = 0; i < y; i++) {
1549 this.insertBlankLine(console.childNodes.length, style);
1552 // Adjust the number of lines in the scrollback buffer by
1553 // removing excess entries.
1554 this.updateNumScrollbackLines();
1555 while (this.numScrollbackLines >
1556 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1557 console.removeChild(console.firstChild);
1558 this.numScrollbackLines--;
1561 // Mark lines in the scrollback buffer, so that they do not get
1563 for (var i = this.numScrollbackLines, j = -incY;
1564 i-- > 0 && j-- > 0; ) {
1565 console.childNodes[i].className = 'scrollback';
1568 // Scrolling up without adding to the scrollback buffer.
1571 console.childNodes.length >
1572 this.numScrollbackLines + y + incY; ) {
1573 console.removeChild(console.childNodes[
1574 this.numScrollbackLines + y + incY]);
1577 // If we used to have a scrollback buffer, then we must make sure
1578 // that we add back blank lines at the bottom of the terminal.
1579 // Similarly, if we are scrolling in the middle of the screen,
1580 // we must add blank lines to ensure that the bottom of the screen
1581 // does not move up.
1582 if (this.numScrollbackLines > 0 ||
1583 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1584 for (var i = -incY; i-- > 0; ) {
1585 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1594 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1595 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1597 for (var i = incY; i--; ) {
1598 this.insertBlankLine(this.numScrollbackLines + y, style);
1602 // Scrolling partial lines
1604 // Scrolling up or horizontally within a line
1605 for (var i = y + this.numScrollbackLines;
1606 i < y + this.numScrollbackLines + h;
1608 this.copyLineSegment(x + incX, i + incY, x, i, w);
1612 for (var i = y + this.numScrollbackLines + h;
1613 i-- > y + this.numScrollbackLines; ) {
1614 this.copyLineSegment(x + incX, i + incY, x, i, w);
1618 // Clear blank regions
1620 this.clearRegion(x, y, incX, h, style);
1621 } else if (incX < 0) {
1622 this.clearRegion(x + w + incX, y, -incX, h, style);
1625 this.clearRegion(x, y, w, incY, style);
1626 } else if (incY < 0) {
1627 this.clearRegion(x, y + h + incY, w, -incY, style);
1631 // Reset scroll position
1632 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1633 this.cursorHeight + 1;
1635 // Move cursor back to its original position
1636 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1640 VT100.prototype.copy = function(selection) {
1641 if (selection == undefined) {
1642 selection = this.selection();
1644 this.internalClipboard = undefined;
1645 if (selection.length) {
1648 this.cliphelper.value = selection;
1649 this.cliphelper.select();
1650 this.cliphelper.createTextRange().execCommand('copy');
1652 this.internalClipboard = selection;
1654 this.cliphelper.value = '';
1658 VT100.prototype.copyLast = function() {
1659 // Opening the context menu can remove the selection. We try to prevent this
1660 // from happening, but that is not possible for all browsers. So, instead,
1661 // we compute the selection before showing the menu.
1662 this.copy(this.lastSelection);
1665 VT100.prototype.pasteFnc = function() {
1666 var clipboard = undefined;
1667 if (this.internalClipboard != undefined) {
1668 clipboard = this.internalClipboard;
1671 this.cliphelper.value = '';
1672 this.cliphelper.createTextRange().execCommand('paste');
1673 clipboard = this.cliphelper.value;
1677 this.cliphelper.value = '';
1678 if (clipboard && this.menu.style.visibility == 'hidden') {
1680 this.keysPressed('' + clipboard);
1687 VT100.prototype.toggleUTF = function() {
1688 this.utfEnabled = !this.utfEnabled;
1691 VT100.prototype.toggleBell = function() {
1692 this.visualBell = !this.visualBell;
1695 VT100.prototype.about = function() {
1696 alert("VT100 Terminal Emulator " + "2.9 (revision 147)" +
1697 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1698 "For more information check http://shellinabox.com");
1701 VT100.prototype.hideContextMenu = function() {
1702 this.menu.style.visibility = 'hidden';
1703 this.menu.style.top = '-100px';
1704 this.menu.style.left = '-100px';
1705 this.menu.style.width = '0px';
1706 this.menu.style.height = '0px';
1709 VT100.prototype.extendContextMenu = function(entries, actions) {
1712 VT100.prototype.showContextMenu = function(x, y) {
1713 this.menu.innerHTML =
1714 '<table class="popup" ' +
1715 'cellpadding="0" cellspacing="0">' +
1717 '<ul id="menuentries">' +
1718 '<li id="beginclipboard">Copy</li>' +
1719 '<li id="endclipboard">Paste</li>' +
1721 '<li id="reset">Reset</li>' +
1723 '<li id="beginconfig">' +
1724 (this.utfEnabled ? '✔ ' : '') + 'Unicode</li>' +
1725 '<li id="endconfig">' +
1726 (this.visualBell ? '✔ ' : '') + 'Visual Bell</li>'+
1728 '<li id="about">About...</li>' +
1733 var popup = this.menu.firstChild;
1734 var menuentries = this.getChildById(popup, 'menuentries');
1736 // Determine menu entries that should be disabled
1737 this.lastSelection = this.selection();
1738 if (!this.lastSelection.length) {
1739 menuentries.firstChild.className
1742 var p = this.pasteFnc();
1744 menuentries.childNodes[1].className
1747 var actions = [ this.copyLast, p, this.reset,
1748 this.toggleUTF, this.toggleBell,
1751 // Allow subclasses to dynamically add entries to the context menu
1752 this.extendContextMenu(menuentries, actions);
1754 // Hook up event listeners
1755 for (var node = menuentries.firstChild, i = 0; node;
1756 node = node.nextSibling) {
1757 if (node.tagName == 'LI') {
1758 if (node.className != 'disabled') {
1759 this.addListener(node, 'mouseover',
1760 function(vt100, node) {
1762 node.className = 'hover';
1765 this.addListener(node, 'mouseout',
1766 function(vt100, node) {
1768 node.className = '';
1771 this.addListener(node, 'mousedown',
1772 function(vt100, action) {
1773 return function(event) {
1774 vt100.hideContextMenu();
1776 return vt100.cancelEvent(event || window.event);
1778 }(this, actions[i]));
1779 this.addListener(node, 'mouseup',
1781 return function(event) {
1782 return vt100.cancelEvent(event || window.event);
1785 this.addListener(node, 'mouseclick',
1787 return function(event) {
1788 return vt100.cancelEvent(event || window.event);
1796 // Position menu next to the mouse pointer
1797 if (x + popup.clientWidth > this.container.offsetWidth) {
1798 x = this.container.offsetWidth - popup.clientWidth;
1803 if (y + popup.clientHeight > this.container.offsetHeight) {
1804 y = this.container.offsetHeight-popup.clientHeight;
1809 popup.style.left = x + 'px';
1810 popup.style.top = y + 'px';
1812 // Block all other interactions with the terminal emulator
1813 this.menu.style.left = '0px';
1814 this.menu.style.top = '0px';
1815 this.menu.style.width = this.container.offsetWidth + 'px';
1816 this.menu.style.height = this.container.offsetHeight + 'px';
1817 this.addListener(this.menu, 'click', function(vt100) {
1819 vt100.hideContextMenu();
1824 this.menu.style.visibility = '';
1827 VT100.prototype.keysPressed = function(ch) {
1828 for (var i = 0; i < ch.length; i++) {
1829 var c = ch.charCodeAt(i);
1830 this.vt100(c >= 7 && c <= 15 ||
1831 c == 24 || c == 26 || c == 27 || c >= 32
1832 ? String.fromCharCode(c) : '<' + c + '>');
1836 VT100.prototype.handleKey = function(event) {
1838 if (typeof event.charCode != 'undefined') {
1839 // non-IE keypress events have a translated charCode value. Also, our
1840 // fake events generated when receiving keydown events include this data
1842 ch = event.charCode;
1843 key = event.keyCode;
1845 // When sending a keypress event, IE includes the translated character
1846 // code in the keyCode field.
1851 // Apply modifier keys (ctrl and shift)
1854 if (event.ctrlKey) {
1855 if (ch >= 32 && ch <= 127) {
1859 if (event.shiftKey) {
1860 if (ch >= 97 && ch <= 122) {
1864 if (ch >= 65 && ch <= 90) {
1873 // By this point, "ch" is either defined and contains the character code, or
1874 // it is undefined and "key" defines the code of a function key
1875 if (ch != undefined) {
1876 ch = String.fromCharCode(ch);
1877 this.scrollable.scrollTop = this.numScrollbackLines *
1878 this.cursorHeight + 1;
1880 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
1881 // Many programs have difficulties dealing with parametrized escape
1882 // sequences for function keys. Thus, if ALT is the only modifier
1883 // key, return Emacs-style keycodes for commonly used keys.
1885 case 33: /* Page Up */ ch = '\u001B<'; break;
1886 case 34: /* Page Down */ ch = '\u001B>'; break;
1887 case 37: /* Left */ ch = '\u001Bb'; break;
1888 case 38: /* Up */ ch = '\u001Bp'; break;
1889 case 39: /* Right */ ch = '\u001Bf'; break;
1890 case 40: /* Down */ ch = '\u001Bn'; break;
1891 case 46: /* Delete */ ch = '\u001Bd'; break;
1894 } else if (event.shiftKey && !event.ctrlKey &&
1895 !event.altKey && !event.metaKey) {
1897 case 33: /* Page Up */ this.scrollBack(); return;
1898 case 34: /* Page Down */ this.scrollFore(); return;
1902 if (ch == undefined) {
1904 case 8: /* Backspace */ ch = '\u007f'; break;
1905 case 9: /* Tab */ ch = '\u0009'; break;
1906 case 10: /* Return */ ch = '\u000A'; break;
1907 case 13: /* Enter */ ch = this.crLfMode ?
1908 '\r\n' : '\r'; break;
1909 case 16: /* Shift */ return;
1910 case 17: /* Ctrl */ return;
1911 case 18: /* Alt */ return;
1912 case 19: /* Break */ return;
1913 case 20: /* Caps Lock */ return;
1914 case 27: /* Escape */ ch = '\u001B'; break;
1915 case 33: /* Page Up */ ch = '\u001B[5~'; break;
1916 case 34: /* Page Down */ ch = '\u001B[6~'; break;
1917 case 35: /* End */ ch = '\u001BOF'; break;
1918 case 36: /* Home */ ch = '\u001BOH'; break;
1919 case 37: /* Left */ ch = this.cursorKeyMode ?
1920 '\u001BOD' : '\u001B[D'; break;
1921 case 38: /* Up */ ch = this.cursorKeyMode ?
1922 '\u001BOA' : '\u001B[A'; break;
1923 case 39: /* Right */ ch = this.cursorKeyMode ?
1924 '\u001BOC' : '\u001B[C'; break;
1925 case 40: /* Down */ ch = this.cursorKeyMode ?
1926 '\u001BOB' : '\u001B[B'; break;
1927 case 45: /* Insert */ ch = '\u001B[2~'; break;
1928 case 46: /* Delete */ ch = '\u001B[3~'; break;
1929 case 91: /* Left Window */ return;
1930 case 92: /* Right Window */ return;
1931 case 93: /* Select */ return;
1932 case 96: /* 0 */ ch = '0'; break;
1933 case 97: /* 1 */ ch = '1'; break;
1934 case 98: /* 2 */ ch = '2'; break;
1935 case 99: /* 3 */ ch = '3'; break;
1936 case 100: /* 4 */ ch = '4'; break;
1937 case 101: /* 5 */ ch = '5'; break;
1938 case 102: /* 6 */ ch = '6'; break;
1939 case 103: /* 7 */ ch = '7'; break;
1940 case 104: /* 8 */ ch = '8'; break;
1941 case 105: /* 9 */ ch = '9'; break;
1942 case 106: /* * */ ch = '*'; break;
1943 case 107: /* + */ ch = '+'; break;
1944 case 109: /* - */ ch = '-'; break;
1945 case 110: /* . */ ch = '.'; break;
1946 case 111: /* / */ ch = '/'; break;
1947 case 112: /* F1 */ ch = '\u001BOP'; break;
1948 case 113: /* F2 */ ch = '\u001BOQ'; break;
1949 case 114: /* F3 */ ch = '\u001BOR'; break;
1950 case 115: /* F4 */ ch = '\u001BOS'; break;
1951 case 116: /* F5 */ ch = '\u001B[15~'; break;
1952 case 117: /* F6 */ ch = '\u001B[17~'; break;
1953 case 118: /* F7 */ ch = '\u001B[18~'; break;
1954 case 119: /* F8 */ ch = '\u001B[19~'; break;
1955 case 120: /* F9 */ ch = '\u001B[20~'; break;
1956 case 121: /* F10 */ ch = '\u001B[21~'; break;
1957 case 122: /* F11 */ ch = '\u001B[23~'; break;
1958 case 123: /* F12 */ ch = '\u001B[24~'; break;
1959 case 144: /* Num Lock */ return;
1960 case 145: /* Scroll Lock */ return;
1963 this.scrollable.scrollTop = this.numScrollbackLines *
1964 this.cursorHeight + 1;
1968 // "ch" now contains the sequence of keycodes to send. But we might still
1969 // have to apply the effects of modifier keys.
1970 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
1971 var start, digit, part1, part2;
1972 if ((start = ch.substr(0, 2)) == '\u001B[') {
1974 part1.length < ch.length &&
1975 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
1976 part1 = ch.substr(0, part1.length + 1);
1978 part2 = ch.substr(part1.length);
1979 if (part1.length > 2) {
1982 } else if (start == '\u001BO') {
1984 part2 = ch.substr(2);
1986 if (part1 != undefined) {
1988 ((event.shiftKey ? 1 : 0) +
1989 (event.altKey|event.metaKey ? 2 : 0) +
1990 (event.ctrlKey ? 4 : 0)) +
1992 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
1997 if (this.menu.style.visibility == 'hidden') {
1998 this.keysPressed(ch);
2002 VT100.prototype.inspect = function(o, d) {
2003 if (d == undefined) {
2007 if (typeof o == 'object' && ++d < 2) {
2010 rc += this.spaces(d * 2) + i + ' -> ';
2012 rc += this.inspect(o[i], d);
2014 rc += '?' + '?' + '?\r\n';
2019 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2024 VT100.prototype.checkComposedKeys = function(event) {
2025 // Composed keys (at least on Linux) do not generate normal events.
2026 // Instead, they get entered into the text field. We normally catch
2027 // this on the next keyup event.
2028 var s = this.input.value;
2030 this.input.value = '';
2031 if (this.menu.style.visibility == 'hidden') {
2032 this.keysPressed(s);
2037 VT100.prototype.fixEvent = function(event) {
2038 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2039 // is used as a second-level selector, clear the modifier bits before
2040 // handling the event.
2041 if (event.ctrlKey && event.altKey) {
2043 fake.charCode = event.charCode;
2044 fake.keyCode = event.keyCode;
2045 fake.ctrlKey = false;
2046 fake.shiftKey = event.shiftKey;
2047 fake.altKey = false;
2048 fake.metaKey = event.metaKey;
2052 // Some browsers fail to translate keys, if both shift and alt/meta is
2053 // pressed at the same time. We try to translate those cases, but that
2054 // only works for US keyboard layouts.
2055 if (event.shiftKey) {
2058 switch (this.lastNormalKeyDownEvent.keyCode) {
2059 case 39: /* ' -> " */ u = 39; s = 34; break;
2060 case 44: /* , -> < */ u = 44; s = 60; break;
2061 case 45: /* - -> _ */ u = 45; s = 95; break;
2062 case 46: /* . -> > */ u = 46; s = 62; break;
2063 case 47: /* / -> ? */ u = 47; s = 63; break;
2065 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2066 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2067 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2068 case 51: /* 3 -> # */ u = 51; s = 35; break;
2069 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2070 case 53: /* 5 -> % */ u = 53; s = 37; break;
2071 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2072 case 55: /* 7 -> & */ u = 55; s = 38; break;
2073 case 56: /* 8 -> * */ u = 56; s = 42; break;
2074 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2076 case 59: /* ; -> : */ u = 59; s = 58; break;
2077 case 61: /* = -> + */ u = 61; s = 43; break;
2078 case 91: /* [ -> { */ u = 91; s = 123; break;
2079 case 92: /* \ -> | */ u = 92; s = 124; break;
2080 case 93: /* ] -> } */ u = 93; s = 125; break;
2081 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2083 case 109: /* - -> _ */ u = 45; s = 95; break;
2084 case 111: /* / -> ? */ u = 47; s = 63; break;
2086 case 186: /* ; -> : */ u = 59; s = 58; break;
2087 case 187: /* = -> + */ u = 61; s = 43; break;
2088 case 188: /* , -> < */ u = 44; s = 60; break;
2089 case 189: /* - -> _ */ u = 45; s = 95; break;
2090 case 190: /* . -> > */ u = 46; s = 62; break;
2091 case 191: /* / -> ? */ u = 47; s = 63; break;
2092 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2093 case 219: /* [ -> { */ u = 91; s = 123; break;
2094 case 220: /* \ -> | */ u = 92; s = 124; break;
2095 case 221: /* ] -> } */ u = 93; s = 125; break;
2096 case 222: /* ' -> " */ u = 39; s = 34; break;
2099 if (s && (event.charCode == u || event.charCode == 0)) {
2102 fake.keyCode = event.keyCode;
2103 fake.ctrlKey = event.ctrlKey;
2104 fake.shiftKey = event.shiftKey;
2105 fake.altKey = event.altKey;
2106 fake.metaKey = event.metaKey;
2113 VT100.prototype.keyDown = function(event) {
2114 this.checkComposedKeys(event);
2115 this.lastKeyPressedEvent = undefined;
2116 this.lastKeyDownEvent = undefined;
2117 this.lastNormalKeyDownEvent = event;
2120 event.keyCode == 32 ||
2121 event.keyCode >= 48 && event.keyCode <= 57 ||
2122 event.keyCode >= 65 && event.keyCode <= 90;
2125 event.keyCode >= 96 && event.keyCode <= 105;
2128 event.keyCode == 59 || event.keyCode == 61 ||
2129 event.keyCode == 106 || event.keyCode == 107 ||
2130 event.keyCode >= 109 && event.keyCode <= 111 ||
2131 event.keyCode >= 186 && event.keyCode <= 192 ||
2132 event.keyCode >= 219 && event.keyCode <= 222 ||
2133 event.keyCode == 226 || event.keyCode == 252;
2135 if (navigator.appName == 'Konqueror') {
2136 normalKey |= event.keyCode < 128;
2141 // We normally prefer to look at keypress events, as they perform the
2142 // translation from keyCode to charCode. This is important, as the
2143 // translation is locale-dependent.
2144 // But for some keys, we must intercept them during the keydown event,
2145 // as they would otherwise get interpreted by the browser.
2146 // Even, when doing all of this, there are some keys that we can never
2147 // intercept. This applies to some of the menu navigation keys in IE.
2148 // In fact, we see them, but we cannot stop IE from seeing them, too.
2149 if ((event.charCode || event.keyCode) &&
2150 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2152 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2153 // interpret this sequence ourselves, as some keyboard layouts use
2154 // it for second-level layouts.
2155 !(event.ctrlKey && event.altKey)) ||
2156 this.catchModifiersEarly && normalKey && !alphNumKey &&
2157 (event.ctrlKey || event.altKey || event.metaKey) ||
2159 this.lastKeyDownEvent = event;
2161 fake.ctrlKey = event.ctrlKey;
2162 fake.shiftKey = event.shiftKey;
2163 fake.altKey = event.altKey;
2164 fake.metaKey = event.metaKey;
2166 fake.charCode = event.keyCode;
2170 fake.keyCode = event.keyCode;
2171 if (!alphNumKey && event.shiftKey) {
2172 fake = this.fixEvent(fake);
2176 this.handleKey(fake);
2177 this.lastNormalKeyDownEvent = undefined;
2180 // For non-IE browsers
2181 event.stopPropagation();
2182 event.preventDefault();
2187 event.cancelBubble = true;
2188 event.returnValue = false;
2198 VT100.prototype.keyPressed = function(event) {
2199 if (this.lastKeyDownEvent) {
2200 // If we already processed the key on keydown, do not process it
2201 // again here. Ideally, the browser should not even have generated a
2202 // keypress event in this case. But that does not appear to always work.
2203 this.lastKeyDownEvent = undefined;
2205 this.handleKey(event.altKey || event.metaKey
2206 ? this.fixEvent(event) : event);
2210 // For non-IE browsers
2211 event.preventDefault();
2217 event.cancelBubble = true;
2218 event.returnValue = false;
2223 this.lastNormalKeyDownEvent = undefined;
2224 this.lastKeyPressedEvent = event;
2228 VT100.prototype.keyUp = function(event) {
2229 if (this.lastKeyPressedEvent) {
2230 // The compose key on Linux occasionally confuses the browser and keeps
2231 // inserting bogus characters into the input field, even if just a regular
2232 // key has been pressed. Detect this case and drop the bogus characters.
2234 event.srcElement).value = '';
2236 // This is usually were we notice that a key has been composed and
2237 // thus failed to generate normal events.
2238 this.checkComposedKeys(event);
2240 // Some browsers don't report keypress events if ctrl or alt is pressed
2241 // for non-alphanumerical keys. Patch things up for now, but in the
2242 // future we will catch these keys earlier (in the keydown handler).
2243 if (this.lastNormalKeyDownEvent) {
2244 this.catchModifiersEarly = true;
2246 event.keyCode == 32 ||
2247 event.keyCode >= 48 && event.keyCode <= 57 ||
2248 event.keyCode >= 65 && event.keyCode <= 90;
2251 event.keyCode >= 96 && event.keyCode <= 105;
2254 event.keyCode == 59 || event.keyCode == 61 ||
2255 event.keyCode == 106 || event.keyCode == 107 ||
2256 event.keyCode >= 109 && event.keyCode <= 111 ||
2257 event.keyCode >= 186 && event.keyCode <= 192 ||
2258 event.keyCode >= 219 && event.keyCode <= 222 ||
2259 event.keyCode == 252;
2261 fake.ctrlKey = event.ctrlKey;
2262 fake.shiftKey = event.shiftKey;
2263 fake.altKey = event.altKey;
2264 fake.metaKey = event.metaKey;
2266 fake.charCode = event.keyCode;
2270 fake.keyCode = event.keyCode;
2271 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2272 fake = this.fixEvent(fake);
2275 this.lastNormalKeyDownEvent = undefined;
2276 this.handleKey(fake);
2282 event.cancelBubble = true;
2283 event.returnValue = false;
2288 this.lastKeyDownEvent = undefined;
2289 this.lastKeyPressedEvent = undefined;
2293 VT100.prototype.animateCursor = function(inactive) {
2294 if (!this.cursorInterval) {
2295 this.cursorInterval = setInterval(
2298 vt100.animateCursor();
2300 // Use this opportunity to check whether the user entered a composed
2301 // key, or whether somebody pasted text into the textfield.
2302 vt100.checkComposedKeys();
2306 if (inactive != undefined || this.cursor.className != 'inactive') {
2308 this.cursor.className = 'inactive';
2310 this.cursor.className = this.cursor.className == 'bright'
2316 VT100.prototype.blurCursor = function() {
2317 this.animateCursor(true);
2320 VT100.prototype.focusCursor = function() {
2321 this.animateCursor(false);
2324 VT100.prototype.flashScreen = function() {
2325 this.isInverted = !this.isInverted;
2326 this.refreshInvertedState();
2327 this.isInverted = !this.isInverted;
2328 setTimeout(function(vt100) {
2330 vt100.refreshInvertedState();
2335 VT100.prototype.beep = function() {
2336 if (this.visualBell) {
2343 this.beeper.src = 'beep.wav';
2350 VT100.prototype.bs = function() {
2351 if (this.cursorX > 0) {
2352 this.gotoXY(this.cursorX - 1, this.cursorY);
2353 this.needWrap = false;
2357 VT100.prototype.ht = function(count) {
2358 if (count == undefined) {
2361 var cx = this.cursorX;
2362 while (count-- > 0) {
2363 while (cx++ < this.terminalWidth) {
2364 var tabState = this.userTabStop[cx];
2365 if (tabState == false) {
2366 // Explicitly cleared tab stop
2368 } else if (tabState) {
2369 // Explicitly set tab stop
2372 // Default tab stop at each eighth column
2379 if (cx > this.terminalWidth - 1) {
2380 cx = this.terminalWidth - 1;
2382 if (cx != this.cursorX) {
2383 this.gotoXY(cx, this.cursorY);
2387 VT100.prototype.rt = function(count) {
2388 if (count == undefined) {
2391 var cx = this.cursorX;
2392 while (count-- > 0) {
2394 var tabState = this.userTabStop[cx];
2395 if (tabState == false) {
2396 // Explicitly cleared tab stop
2398 } else if (tabState) {
2399 // Explicitly set tab stop
2402 // Default tab stop at each eighth column
2412 if (cx != this.cursorX) {
2413 this.gotoXY(cx, this.cursorY);
2417 VT100.prototype.cr = function() {
2418 this.gotoXY(0, this.cursorY);
2419 this.needWrap = false;
2422 VT100.prototype.lf = function(count) {
2423 if (count == undefined) {
2426 if (count > this.terminalHeight) {
2427 count = this.terminalHeight;
2433 while (count-- > 0) {
2434 if (this.cursorY == this.bottom - 1) {
2435 this.scrollRegion(0, this.top + 1,
2436 this.terminalWidth, this.bottom - this.top - 1,
2439 } else if (this.cursorY < this.terminalHeight - 1) {
2440 this.gotoXY(this.cursorX, this.cursorY + 1);
2445 VT100.prototype.ri = function(count) {
2446 if (count == undefined) {
2449 if (count > this.terminalHeight) {
2450 count = this.terminalHeight;
2456 while (count-- > 0) {
2457 if (this.cursorY == this.top) {
2458 this.scrollRegion(0, this.top,
2459 this.terminalWidth, this.bottom - this.top - 1,
2461 } else if (this.cursorY > 0) {
2462 this.gotoXY(this.cursorX, this.cursorY - 1);
2465 this.needWrap = false;
2468 VT100.prototype.respondID = function() {
2469 this.respondString += '\u001B[?6c';
2472 VT100.prototype.respondSecondaryDA = function() {
2473 this.respondString += '\u001B[>0;0;0c';
2476 VT100.prototype.updateStyle = function() {
2478 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2479 style += 'text-decoration:underline;';
2481 var bg = (this.attr >> 4) & 0xF;
2482 var fg = this.attr & 0xF;
2483 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2488 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2489 fg = 8; // Dark grey
2490 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2493 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2496 // Make some readability enhancements. Most notably, disallow identical
2497 // background and foreground colors.
2499 if ((fg ^= 8) == 7) {
2503 // And disallow bright colors on a light-grey background.
2504 if (bg == 7 && fg >= 8) {
2505 if ((fg -= 8) == 7) {
2511 style += 'color:' + this.ansi[fg] + ';';
2514 style += 'background-color:' + this.ansi[bg] + ';';
2516 this.attributeHelper.cssText = style;
2517 this.style = this.attributeHelper.cssText;
2520 VT100.prototype.setAttrColors = function(attr) {
2521 if (attr != this.attr) {
2527 VT100.prototype.saveCursor = function() {
2528 this.savedX[this.currentScreen] = this.cursorX;
2529 this.savedY[this.currentScreen] = this.cursorY;
2530 this.savedAttr[this.currentScreen] = this.attr;
2531 this.savedUseGMap = this.useGMap;
2532 for (var i = 0; i < 4; i++) {
2533 this.savedGMap[i] = this.GMap[i];
2535 this.savedValid[this.currentScreen] = true;
2538 VT100.prototype.restoreCursor = function() {
2539 if (!this.savedValid[this.currentScreen]) {
2542 this.attr = this.savedAttr[this.currentScreen];
2544 this.useGMap = this.savedUseGMap;
2545 for (var i = 0; i < 4; i++) {
2546 this.GMap[i] = this.savedGMap[i];
2548 this.translate = this.GMap[this.useGMap];
2549 this.needWrap = false;
2550 this.gotoXY(this.savedX[this.currentScreen],
2551 this.savedY[this.currentScreen]);
2554 VT100.prototype.setMode = function(state) {
2555 for (var i = 0; i <= this.npar; i++) {
2556 if (this.isQuestionMark) {
2557 switch (this.par[i]) {
2558 case 1: this.cursorKeyMode = state; break;
2559 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2560 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2561 case 6: this.offsetMode = state; break;
2562 case 7: this.autoWrapMode = state; break;
2564 case 9: this.mouseReporting = state; break;
2565 case 25: this.cursorNeedsShowing = state;
2566 if (state) { this.showCursor(); }
2567 else { this.hideCursor(); } break;
2570 case 47: this.enableAlternateScreen(state); break;
2574 switch (this.par[i]) {
2575 case 3: this.dispCtrl = state; break;
2576 case 4: this.insertMode = state; break;
2577 case 20:this.crLfMode = state; break;
2584 VT100.prototype.statusReport = function() {
2585 // Ready and operational.
2586 this.respondString += '\u001B[0n';
2589 VT100.prototype.cursorReport = function() {
2590 this.respondString += '\u001B[' +
2591 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2593 (this.cursorX + 1) +
2597 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2598 // Changing of cursor color is not implemented.
2601 VT100.prototype.csiAt = function(number) {
2606 if (number > this.terminalWidth - this.cursorX) {
2607 number = this.terminalWidth - this.cursorX;
2609 this.scrollRegion(this.cursorX, this.cursorY,
2610 this.terminalWidth - this.cursorX - number, 1,
2611 number, 0, this.style);
2612 this.needWrap = false;
2615 VT100.prototype.csiJ = function(number) {
2617 case 0: // Erase from cursor to end of display
2618 this.clearRegion(this.cursorX, this.cursorY,
2619 this.terminalWidth - this.cursorX, 1, this.style);
2620 if (this.cursorY < this.terminalHeight-2) {
2621 this.clearRegion(0, this.cursorY+1,
2622 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2626 case 1: // Erase from start to cursor
2627 if (this.cursorY > 0) {
2628 this.clearRegion(0, 0,
2629 this.terminalWidth, this.cursorY, this.style);
2631 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2633 case 2: // Erase whole display
2634 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
2642 VT100.prototype.csiK = function(number) {
2644 case 0: // Erase from cursor to end of line
2645 this.clearRegion(this.cursorX, this.cursorY,
2646 this.terminalWidth - this.cursorX, 1, this.style);
2648 case 1: // Erase from start of line to cursor
2649 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2651 case 2: // Erase whole line
2652 this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
2660 VT100.prototype.csiL = function(number) {
2661 // Open line by inserting blank line(s)
2662 if (this.cursorY >= this.bottom) {
2668 if (number > this.bottom - this.cursorY) {
2669 number = this.bottom - this.cursorY;
2671 this.scrollRegion(0, this.cursorY,
2672 this.terminalWidth, this.bottom - this.cursorY - number,
2673 0, number, this.style);
2677 VT100.prototype.csiM = function(number) {
2678 // Delete line(s), scrolling up the bottom of the screen.
2679 if (this.cursorY >= this.bottom) {
2685 if (number > this.bottom - this.cursorY) {
2686 number = bottom - cursorY;
2688 this.scrollRegion(0, this.cursorY + number,
2689 this.terminalWidth, this.bottom - this.cursorY - number,
2690 0, -number, this.style);
2694 VT100.prototype.csim = function() {
2695 for (var i = 0; i <= this.npar; i++) {
2696 switch (this.par[i]) {
2697 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2698 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2699 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2700 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2701 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2702 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
2704 this.translate = this.GMap[this.useGMap];
2705 this.dispCtrl = false;
2706 this.toggleMeta = false;
2709 this.translate = this.CodePage437Map;
2710 this.dispCtrl = true;
2711 this.toggleMeta = false;
2714 this.translate = this.CodePage437Map;
2715 this.dispCtrl = true;
2716 this.toggleMeta = true;
2719 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2720 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2721 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2722 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2723 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2724 0x0200 /* ATTR_UNDERLINE */; break;
2725 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
2726 case 49: this.attr |= 0xF0; break;
2728 if (this.par[i] >= 30 && this.par[i] <= 37) {
2729 var fg = this.par[i] - 30;
2730 this.attr = (this.attr & ~0x0F) | fg;
2731 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2732 var bg = this.par[i] - 40;
2733 this.attr = (this.attr & ~0xF0) | (bg << 4);
2741 VT100.prototype.csiP = function(number) {
2742 // Delete character(s) following cursor
2746 if (number > this.terminalWidth - this.cursorX) {
2747 number = this.terminalWidth - this.cursorX;
2749 this.scrollRegion(this.cursorX + number, this.cursorY,
2750 this.terminalWidth - this.cursorX - number, 1,
2751 -number, 0, this.style);
2755 VT100.prototype.csiX = function(number) {
2756 // Clear characters following cursor
2760 if (number > this.terminalWidth - this.cursorX) {
2761 number = this.terminalWidth - this.cursorX;
2763 this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
2767 VT100.prototype.settermCommand = function() {
2768 // Setterm commands are not implemented
2771 VT100.prototype.doControl = function(ch) {
2774 case 0x00: /* ignored */ break;
2775 case 0x08: this.bs(); break;
2776 case 0x09: this.ht(); break;
2780 case 0x84: this.lf(); if (!this.crLfMode) break;
2781 case 0x0D: this.cr(); break;
2782 case 0x85: this.cr(); this.lf(); break;
2783 case 0x0E: this.useGMap = 1;
2784 this.translate = this.GMap[1];
2785 this.dispCtrl = true; break;
2786 case 0x0F: this.useGMap = 0;
2787 this.translate = this.GMap[0];
2788 this.dispCtrl = false; break;
2790 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
2791 case 0x1B: this.isEsc = 1 /* ESesc */; break;
2792 case 0x7F: /* ignored */ break;
2793 case 0x88: this.userTabStop[this.cursorX] = true; break;
2794 case 0x8D: this.ri(); break;
2795 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
2796 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
2797 case 0x9A: this.respondID(); break;
2798 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
2799 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
2803 default: switch (this.isEsc) {
2805 this.isEsc = 0 /* ESnormal */;
2807 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
2808 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
2810 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
2812 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
2814 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
2815 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
2816 /*7*/ case 0x37: this.saveCursor(); break;
2817 /*8*/ case 0x38: this.restoreCursor(); break;
2818 /*>*/ case 0x3E: this.applKeyMode = false; break;
2819 /*=*/ case 0x3D: this.applKeyMode = true; break;
2820 /*D*/ case 0x44: this.lf(); break;
2821 /*E*/ case 0x45: this.cr(); this.lf(); break;
2822 /*M*/ case 0x4D: this.ri(); break;
2823 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
2824 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
2825 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
2826 /*Z*/ case 0x5A: this.respondID(); break;
2827 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
2828 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
2829 /*c*/ case 0x63: this.reset(); break;
2830 /*g*/ case 0x67: this.flashScreen(); break;
2834 case 15 /* ESnonstd */:
2838 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
2839 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
2840 this.isEsc = 16 /* ESpalette */; break;
2841 /*R*/ case 0x52: // Palette support is not implemented
2842 this.isEsc = 0 /* ESnormal */; break;
2843 default: this.isEsc = 0 /* ESnormal */; break;
2846 case 16 /* ESpalette */:
2847 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
2848 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
2849 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
2850 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
2852 if (this.npar == 7) {
2853 // Palette support is not implemented
2854 this.isEsc = 0 /* ESnormal */;
2857 this.isEsc = 0 /* ESnormal */;
2860 case 2 /* ESsquare */:
2862 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
2863 0, 0, 0, 0, 0, 0, 0, 0 ];
2864 this.isEsc = 3 /* ESgetpars */;
2865 /*[*/ if (ch == 0x5B) { // Function key
2866 this.isEsc = 6 /* ESfunckey */;
2869 /*?*/ this.isQuestionMark = ch == 0x3F;
2870 if (this.isQuestionMark) {
2875 case 5 /* ESdeviceattr */:
2876 case 3 /* ESgetpars */:
2877 /*;*/ if (ch == 0x3B) {
2880 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
2881 var par = this.par[this.npar];
2882 if (par == undefined) {
2885 this.par[this.npar] = 10*par + (ch & 0xF);
2887 } else if (this.isEsc == 5 /* ESdeviceattr */) {
2889 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
2890 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
2891 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
2892 /*p*/ case 0x70: /* set pointer mode resource value */ break;
2895 this.isEsc = 0 /* ESnormal */;
2898 this.isEsc = 4 /* ESgotpars */;
2901 case 4 /* ESgotpars */:
2902 this.isEsc = 0 /* ESnormal */;
2903 if (this.isQuestionMark) {
2905 /*h*/ case 0x68: this.setMode(true); break;
2906 /*l*/ case 0x6C: this.setMode(false); break;
2907 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
2910 this.isQuestionMark = false;
2914 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
2915 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
2917 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
2918 /*A*/ case 0x41: this.gotoXY(this.cursorX,
2919 this.cursorY - (this.par[0] ? this.par[0] : 1));
2922 /*e*/ case 0x65: this.gotoXY(this.cursorX,
2923 this.cursorY + (this.par[0] ? this.par[0] : 1));
2926 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
2927 this.cursorY); break;
2928 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
2929 this.cursorY); break;
2930 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
2932 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
2934 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
2936 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
2937 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
2938 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
2939 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
2940 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
2941 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
2942 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
2943 /*m*/ case 0x6D: this.csim(); break;
2944 /*P*/ case 0x50: this.csiP(this.par[0]); break;
2945 /*X*/ case 0x58: this.csiX(this.par[0]); break;
2946 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
2947 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
2948 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
2949 /*g*/ case 0x67: if (this.par[0] == 0) {
2950 this.userTabStop[this.cursorX] = false;
2951 } else if (this.par[0] == 2 || this.par[0] == 3) {
2952 this.userTabStop = [ ];
2953 for (var i = 0; i < this.terminalWidth; i++) {
2954 this.userTabStop[i] = false;
2958 /*h*/ case 0x68: this.setMode(true); break;
2959 /*l*/ case 0x6C: this.setMode(false); break;
2960 /*n*/ case 0x6E: switch (this.par[0]) {
2961 case 5: this.statusReport(); break;
2962 case 6: this.cursorReport(); break;
2966 /*q*/ case 0x71: // LED control not implemented
2968 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
2969 var b = this.par[1] ? this.par[1]
2970 : this.terminalHeight;
2971 if (t < b && b <= this.terminalHeight) {
2977 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
2978 if (c > this.terminalWidth * this.terminalHeight) {
2979 c = this.terminalWidth * this.terminalHeight;
2982 lineBuf += this.lastCharacter;
2985 /*s*/ case 0x73: this.saveCursor(); break;
2986 /*u*/ case 0x75: this.restoreCursor(); break;
2987 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
2988 /*]*/ case 0x5D: this.settermCommand(); break;
2992 case 12 /* ESbang */:
2996 this.isEsc = 0 /* ESnormal */;
2998 case 13 /* ESpercent */:
2999 this.isEsc = 0 /* ESnormal */;
3001 /*@*/ case 0x40: this.utfEnabled = false; break;
3003 /*8*/ case 0x38: this.utfEnabled = true; break;
3007 case 6 /* ESfunckey */:
3008 this.isEsc = 0 /* ESnormal */; break;
3009 case 7 /* EShash */:
3010 this.isEsc = 0 /* ESnormal */;
3011 /*8*/ if (ch == 0x38) {
3012 // Screen alignment test not implemented
3015 case 8 /* ESsetG0 */:
3016 case 9 /* ESsetG1 */:
3017 case 10 /* ESsetG2 */:
3018 case 11 /* ESsetG3 */:
3019 var g = this.isEsc - 8 /* ESsetG0 */;
3020 this.isEsc = 0 /* ESnormal */;
3022 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3024 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3025 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3026 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3029 if (this.useGMap == g) {
3030 this.translate = this.GMap[g];
3033 case 17 /* ESstatus */:
3035 if (this.statusString && this.statusString.charAt(0) == ';') {
3036 this.statusString = this.statusString.substr(1);
3039 window.status = this.statusString;
3042 this.isEsc = 0 /* ESnormal */;
3044 this.statusString += String.fromCharCode(ch);
3047 case 18 /* ESss2 */:
3048 case 19 /* ESss3 */:
3050 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3051 [this.toggleMeta ? (ch | 0x80) : ch];
3052 if ((ch & 0xFF00) == 0xF000) {
3054 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3055 this.isEsc = 0 /* ESnormal */; break;
3058 this.lastCharacter = String.fromCharCode(ch);
3059 lineBuf += this.lastCharacter;
3060 this.isEsc = 0 /* ESnormal */; break;
3062 this.isEsc = 0 /* ESnormal */; break;
3069 VT100.prototype.renderString = function(s, showCursor) {
3070 // We try to minimize the number of DOM operations by coalescing individual
3071 // characters into strings. This is a significant performance improvement.
3072 var incX = s.length;
3073 if (incX > this.terminalWidth - this.cursorX) {
3074 incX = this.terminalWidth - this.cursorX;
3078 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3081 // Minimize the number of calls to putString(), by avoiding a direct
3082 // call to this.showCursor()
3083 this.cursor.style.visibility = '';
3085 this.putString(this.cursorX, this.cursorY, s, this.style);
3088 VT100.prototype.vt100 = function(s) {
3089 this.cursorNeedsShowing = this.hideCursor();
3090 this.respondString = '';
3092 for (var i = 0; i < s.length; i++) {
3093 var ch = s.charCodeAt(i);
3094 if (this.utfEnabled) {
3095 // Decode UTF8 encoded character
3097 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3098 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3099 if (--this.utfCount <= 0) {
3100 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3109 if ((ch & 0xE0) == 0xC0) {
3111 this.utfChar = ch & 0x1F;
3112 } else if ((ch & 0xF0) == 0xE0) {
3114 this.utfChar = ch & 0x0F;
3115 } else if ((ch & 0xF8) == 0xF0) {
3117 this.utfChar = ch & 0x07;
3118 } else if ((ch & 0xFC) == 0xF8) {
3120 this.utfChar = ch & 0x03;
3121 } else if ((ch & 0xFE) == 0xFC) {
3123 this.utfChar = ch & 0x01;
3133 var isNormalCharacter =
3134 (ch >= 32 && ch <= 127 || ch >= 160 ||
3135 this.utfEnabled && ch >= 128 ||
3136 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3137 (ch != 0x7F || this.dispCtrl);
3139 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3141 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3143 if ((ch & 0xFF00) == 0xF000) {
3145 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3148 if (this.needWrap || this.insertMode) {
3150 this.renderString(lineBuf);
3154 if (this.needWrap) {
3155 this.cr(); this.lf();
3157 if (this.insertMode) {
3158 this.scrollRegion(this.cursorX, this.cursorY,
3159 this.terminalWidth - this.cursorX - 1, 1,
3162 this.lastCharacter = String.fromCharCode(ch);
3163 lineBuf += this.lastCharacter;
3164 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3165 this.needWrap = this.autoWrapMode;
3169 this.renderString(lineBuf);
3172 var expand = this.doControl(ch);
3173 if (expand.length) {
3174 var r = this.respondString;
3175 this.respondString= r + this.vt100(expand);
3180 this.renderString(lineBuf, this.cursorNeedsShowing);
3181 } else if (this.cursorNeedsShowing) {
3184 return this.respondString;
3187 VT100.prototype.Latin1Map = [
3188 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3189 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3190 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3191 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3192 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3193 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3194 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3195 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3196 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3197 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3198 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3199 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3200 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3201 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3202 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3203 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3204 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3205 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3206 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3207 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3208 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3209 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3210 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3211 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3212 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3213 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3214 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3215 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3216 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3217 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3218 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3219 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3222 VT100.prototype.VT100GraphicsMap = [
3223 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3224 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3225 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3226 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3227 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3228 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3229 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3230 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3231 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3232 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3233 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3234 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3235 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3236 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3237 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3238 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3239 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3240 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3241 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3242 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3243 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3244 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3245 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3246 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3247 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3248 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3249 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3250 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3251 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3252 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3253 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3254 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3257 VT100.prototype.CodePage437Map = [
3258 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3259 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3260 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3261 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3262 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3263 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3264 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3265 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3266 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3267 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3268 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3269 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3270 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3271 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3272 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3273 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3274 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3275 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3276 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3277 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3278 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3279 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3280 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3281 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3282 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3283 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3284 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3285 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3286 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3287 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3288 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3289 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3292 VT100.prototype.DirectToFontMap = [
3293 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3294 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3295 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3296 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3297 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3298 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3299 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3300 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3301 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3302 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3303 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3304 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3305 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3306 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3307 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3308 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3309 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3310 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3311 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3312 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3313 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3314 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3315 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3316 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3317 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3318 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3319 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3320 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3321 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3322 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3323 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3324 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3327 VT100.prototype.ctrlAction = [
3328 true, false, false, false, false, false, false, true,
3329 true, true, true, true, true, true, true, true,
3330 false, false, false, false, false, false, false, false,
3331 true, false, true, true, false, false, false, false
3334 VT100.prototype.ctrlAlways = [
3335 true, false, false, false, false, false, false, false,
3336 true, false, true, false, true, true, true, true,
3337 false, false, false, false, false, false, false, false,
3338 false, false, false, true, false, false, false, false