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 164)" +
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.vt100('R: c=');
1999 // for (var i = 0; i < ch.length; i++)
2000 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2001 // this.vt100('\r\n');
2002 this.keysPressed(ch);
2006 VT100.prototype.inspect = function(o, d) {
2007 if (d == undefined) {
2011 if (typeof o == 'object' && ++d < 2) {
2014 rc += this.spaces(d * 2) + i + ' -> ';
2016 rc += this.inspect(o[i], d);
2018 rc += '?' + '?' + '?\r\n';
2023 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2028 VT100.prototype.checkComposedKeys = function(event) {
2029 // Composed keys (at least on Linux) do not generate normal events.
2030 // Instead, they get entered into the text field. We normally catch
2031 // this on the next keyup event.
2032 var s = this.input.value;
2034 this.input.value = '';
2035 if (this.menu.style.visibility == 'hidden') {
2036 this.keysPressed(s);
2041 VT100.prototype.fixEvent = function(event) {
2042 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2043 // is used as a second-level selector, clear the modifier bits before
2044 // handling the event.
2045 if (event.ctrlKey && event.altKey) {
2047 fake.charCode = event.charCode;
2048 fake.keyCode = event.keyCode;
2049 fake.ctrlKey = false;
2050 fake.shiftKey = event.shiftKey;
2051 fake.altKey = false;
2052 fake.metaKey = event.metaKey;
2056 // Some browsers fail to translate keys, if both shift and alt/meta is
2057 // pressed at the same time. We try to translate those cases, but that
2058 // only works for US keyboard layouts.
2059 if (event.shiftKey) {
2062 switch (this.lastNormalKeyDownEvent.keyCode) {
2063 case 39: /* ' -> " */ u = 39; s = 34; break;
2064 case 44: /* , -> < */ u = 44; s = 60; break;
2065 case 45: /* - -> _ */ u = 45; s = 95; break;
2066 case 46: /* . -> > */ u = 46; s = 62; break;
2067 case 47: /* / -> ? */ u = 47; s = 63; break;
2069 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2070 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2071 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2072 case 51: /* 3 -> # */ u = 51; s = 35; break;
2073 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2074 case 53: /* 5 -> % */ u = 53; s = 37; break;
2075 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2076 case 55: /* 7 -> & */ u = 55; s = 38; break;
2077 case 56: /* 8 -> * */ u = 56; s = 42; break;
2078 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2080 case 59: /* ; -> : */ u = 59; s = 58; break;
2081 case 61: /* = -> + */ u = 61; s = 43; break;
2082 case 91: /* [ -> { */ u = 91; s = 123; break;
2083 case 92: /* \ -> | */ u = 92; s = 124; break;
2084 case 93: /* ] -> } */ u = 93; s = 125; break;
2085 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2087 case 109: /* - -> _ */ u = 45; s = 95; break;
2088 case 111: /* / -> ? */ u = 47; s = 63; break;
2090 case 186: /* ; -> : */ u = 59; s = 58; break;
2091 case 187: /* = -> + */ u = 61; s = 43; break;
2092 case 188: /* , -> < */ u = 44; s = 60; break;
2093 case 189: /* - -> _ */ u = 45; s = 95; break;
2094 case 190: /* . -> > */ u = 46; s = 62; break;
2095 case 191: /* / -> ? */ u = 47; s = 63; break;
2096 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2097 case 219: /* [ -> { */ u = 91; s = 123; break;
2098 case 220: /* \ -> | */ u = 92; s = 124; break;
2099 case 221: /* ] -> } */ u = 93; s = 125; break;
2100 case 222: /* ' -> " */ u = 39; s = 34; break;
2103 if (s && (event.charCode == u || event.charCode == 0)) {
2106 fake.keyCode = event.keyCode;
2107 fake.ctrlKey = event.ctrlKey;
2108 fake.shiftKey = event.shiftKey;
2109 fake.altKey = event.altKey;
2110 fake.metaKey = event.metaKey;
2117 VT100.prototype.keyDown = function(event) {
2118 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2119 // (event.shiftKey || event.ctrlKey || event.altKey ||
2120 // event.metaKey ? ', ' +
2121 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2122 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2124 this.checkComposedKeys(event);
2125 this.lastKeyPressedEvent = undefined;
2126 this.lastKeyDownEvent = undefined;
2127 this.lastNormalKeyDownEvent = event;
2130 event.keyCode == 32 ||
2131 event.keyCode >= 48 && event.keyCode <= 57 ||
2132 event.keyCode >= 65 && event.keyCode <= 90;
2135 event.keyCode >= 96 && event.keyCode <= 105;
2138 event.keyCode == 59 || event.keyCode == 61 ||
2139 event.keyCode == 106 || event.keyCode == 107 ||
2140 event.keyCode >= 109 && event.keyCode <= 111 ||
2141 event.keyCode >= 186 && event.keyCode <= 192 ||
2142 event.keyCode >= 219 && event.keyCode <= 222 ||
2143 event.keyCode == 226 || event.keyCode == 252;
2145 if (navigator.appName == 'Konqueror') {
2146 normalKey |= event.keyCode < 128;
2151 // We normally prefer to look at keypress events, as they perform the
2152 // translation from keyCode to charCode. This is important, as the
2153 // translation is locale-dependent.
2154 // But for some keys, we must intercept them during the keydown event,
2155 // as they would otherwise get interpreted by the browser.
2156 // Even, when doing all of this, there are some keys that we can never
2157 // intercept. This applies to some of the menu navigation keys in IE.
2158 // In fact, we see them, but we cannot stop IE from seeing them, too.
2159 if ((event.charCode || event.keyCode) &&
2160 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2162 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2163 // interpret this sequence ourselves, as some keyboard layouts use
2164 // it for second-level layouts.
2165 !(event.ctrlKey && event.altKey)) ||
2166 this.catchModifiersEarly && normalKey && !alphNumKey &&
2167 (event.ctrlKey || event.altKey || event.metaKey) ||
2169 this.lastKeyDownEvent = event;
2171 fake.ctrlKey = event.ctrlKey;
2172 fake.shiftKey = event.shiftKey;
2173 fake.altKey = event.altKey;
2174 fake.metaKey = event.metaKey;
2176 fake.charCode = event.keyCode;
2180 fake.keyCode = event.keyCode;
2181 if (!alphNumKey && event.shiftKey) {
2182 fake = this.fixEvent(fake);
2186 this.handleKey(fake);
2187 this.lastNormalKeyDownEvent = undefined;
2190 // For non-IE browsers
2191 event.stopPropagation();
2192 event.preventDefault();
2197 event.cancelBubble = true;
2198 event.returnValue = false;
2208 VT100.prototype.keyPressed = function(event) {
2209 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2210 // (event.shiftKey || event.ctrlKey || event.altKey ||
2211 // event.metaKey ? ', ' +
2212 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2213 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2215 if (this.lastKeyDownEvent) {
2216 // If we already processed the key on keydown, do not process it
2217 // again here. Ideally, the browser should not even have generated a
2218 // keypress event in this case. But that does not appear to always work.
2219 this.lastKeyDownEvent = undefined;
2221 this.handleKey(event.altKey || event.metaKey
2222 ? this.fixEvent(event) : event);
2226 // For non-IE browsers
2227 event.preventDefault();
2233 event.cancelBubble = true;
2234 event.returnValue = false;
2239 this.lastNormalKeyDownEvent = undefined;
2240 this.lastKeyPressedEvent = event;
2244 VT100.prototype.keyUp = function(event) {
2245 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2246 // (event.shiftKey || event.ctrlKey || event.altKey ||
2247 // event.metaKey ? ', ' +
2248 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2249 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2251 if (this.lastKeyPressedEvent) {
2252 // The compose key on Linux occasionally confuses the browser and keeps
2253 // inserting bogus characters into the input field, even if just a regular
2254 // key has been pressed. Detect this case and drop the bogus characters.
2256 event.srcElement).value = '';
2258 // This is usually were we notice that a key has been composed and
2259 // thus failed to generate normal events.
2260 this.checkComposedKeys(event);
2262 // Some browsers don't report keypress events if ctrl or alt is pressed
2263 // for non-alphanumerical keys. Patch things up for now, but in the
2264 // future we will catch these keys earlier (in the keydown handler).
2265 if (this.lastNormalKeyDownEvent) {
2266 this.catchModifiersEarly = true;
2268 event.keyCode == 32 ||
2269 event.keyCode >= 48 && event.keyCode <= 57 ||
2270 event.keyCode >= 65 && event.keyCode <= 90;
2273 event.keyCode >= 96 && event.keyCode <= 105;
2276 event.keyCode == 59 || event.keyCode == 61 ||
2277 event.keyCode == 106 || event.keyCode == 107 ||
2278 event.keyCode >= 109 && event.keyCode <= 111 ||
2279 event.keyCode >= 186 && event.keyCode <= 192 ||
2280 event.keyCode >= 219 && event.keyCode <= 222 ||
2281 event.keyCode == 252;
2283 fake.ctrlKey = event.ctrlKey;
2284 fake.shiftKey = event.shiftKey;
2285 fake.altKey = event.altKey;
2286 fake.metaKey = event.metaKey;
2288 fake.charCode = event.keyCode;
2292 fake.keyCode = event.keyCode;
2293 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2294 fake = this.fixEvent(fake);
2297 this.lastNormalKeyDownEvent = undefined;
2298 this.handleKey(fake);
2304 event.cancelBubble = true;
2305 event.returnValue = false;
2310 this.lastKeyDownEvent = undefined;
2311 this.lastKeyPressedEvent = undefined;
2315 VT100.prototype.animateCursor = function(inactive) {
2316 if (!this.cursorInterval) {
2317 this.cursorInterval = setInterval(
2320 vt100.animateCursor();
2322 // Use this opportunity to check whether the user entered a composed
2323 // key, or whether somebody pasted text into the textfield.
2324 vt100.checkComposedKeys();
2328 if (inactive != undefined || this.cursor.className != 'inactive') {
2330 this.cursor.className = 'inactive';
2332 this.cursor.className = this.cursor.className == 'bright'
2338 VT100.prototype.blurCursor = function() {
2339 this.animateCursor(true);
2342 VT100.prototype.focusCursor = function() {
2343 this.animateCursor(false);
2346 VT100.prototype.flashScreen = function() {
2347 this.isInverted = !this.isInverted;
2348 this.refreshInvertedState();
2349 this.isInverted = !this.isInverted;
2350 setTimeout(function(vt100) {
2352 vt100.refreshInvertedState();
2357 VT100.prototype.beep = function() {
2358 if (this.visualBell) {
2365 this.beeper.src = 'beep.wav';
2372 VT100.prototype.bs = function() {
2373 if (this.cursorX > 0) {
2374 this.gotoXY(this.cursorX - 1, this.cursorY);
2375 this.needWrap = false;
2379 VT100.prototype.ht = function(count) {
2380 if (count == undefined) {
2383 var cx = this.cursorX;
2384 while (count-- > 0) {
2385 while (cx++ < this.terminalWidth) {
2386 var tabState = this.userTabStop[cx];
2387 if (tabState == false) {
2388 // Explicitly cleared tab stop
2390 } else if (tabState) {
2391 // Explicitly set tab stop
2394 // Default tab stop at each eighth column
2401 if (cx > this.terminalWidth - 1) {
2402 cx = this.terminalWidth - 1;
2404 if (cx != this.cursorX) {
2405 this.gotoXY(cx, this.cursorY);
2409 VT100.prototype.rt = function(count) {
2410 if (count == undefined) {
2413 var cx = this.cursorX;
2414 while (count-- > 0) {
2416 var tabState = this.userTabStop[cx];
2417 if (tabState == false) {
2418 // Explicitly cleared tab stop
2420 } else if (tabState) {
2421 // Explicitly set tab stop
2424 // Default tab stop at each eighth column
2434 if (cx != this.cursorX) {
2435 this.gotoXY(cx, this.cursorY);
2439 VT100.prototype.cr = function() {
2440 this.gotoXY(0, this.cursorY);
2441 this.needWrap = false;
2444 VT100.prototype.lf = function(count) {
2445 if (count == undefined) {
2448 if (count > this.terminalHeight) {
2449 count = this.terminalHeight;
2455 while (count-- > 0) {
2456 if (this.cursorY == this.bottom - 1) {
2457 this.scrollRegion(0, this.top + 1,
2458 this.terminalWidth, this.bottom - this.top - 1,
2461 } else if (this.cursorY < this.terminalHeight - 1) {
2462 this.gotoXY(this.cursorX, this.cursorY + 1);
2467 VT100.prototype.ri = function(count) {
2468 if (count == undefined) {
2471 if (count > this.terminalHeight) {
2472 count = this.terminalHeight;
2478 while (count-- > 0) {
2479 if (this.cursorY == this.top) {
2480 this.scrollRegion(0, this.top,
2481 this.terminalWidth, this.bottom - this.top - 1,
2483 } else if (this.cursorY > 0) {
2484 this.gotoXY(this.cursorX, this.cursorY - 1);
2487 this.needWrap = false;
2490 VT100.prototype.respondID = function() {
2491 this.respondString += '\u001B[?6c';
2494 VT100.prototype.respondSecondaryDA = function() {
2495 this.respondString += '\u001B[>0;0;0c';
2498 VT100.prototype.updateStyle = function() {
2500 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2501 style += 'text-decoration:underline;';
2503 var bg = (this.attr >> 4) & 0xF;
2504 var fg = this.attr & 0xF;
2505 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2510 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2511 fg = 8; // Dark grey
2512 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2515 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2518 // Make some readability enhancements. Most notably, disallow identical
2519 // background and foreground colors.
2521 if ((fg ^= 8) == 7) {
2525 // And disallow bright colors on a light-grey background.
2526 if (bg == 7 && fg >= 8) {
2527 if ((fg -= 8) == 7) {
2533 style += 'color:' + this.ansi[fg] + ';';
2536 style += 'background-color:' + this.ansi[bg] + ';';
2538 this.attributeHelper.cssText = style;
2539 this.style = this.attributeHelper.cssText;
2542 VT100.prototype.setAttrColors = function(attr) {
2543 if (attr != this.attr) {
2549 VT100.prototype.saveCursor = function() {
2550 this.savedX[this.currentScreen] = this.cursorX;
2551 this.savedY[this.currentScreen] = this.cursorY;
2552 this.savedAttr[this.currentScreen] = this.attr;
2553 this.savedUseGMap = this.useGMap;
2554 for (var i = 0; i < 4; i++) {
2555 this.savedGMap[i] = this.GMap[i];
2557 this.savedValid[this.currentScreen] = true;
2560 VT100.prototype.restoreCursor = function() {
2561 if (!this.savedValid[this.currentScreen]) {
2564 this.attr = this.savedAttr[this.currentScreen];
2566 this.useGMap = this.savedUseGMap;
2567 for (var i = 0; i < 4; i++) {
2568 this.GMap[i] = this.savedGMap[i];
2570 this.translate = this.GMap[this.useGMap];
2571 this.needWrap = false;
2572 this.gotoXY(this.savedX[this.currentScreen],
2573 this.savedY[this.currentScreen]);
2576 VT100.prototype.setMode = function(state) {
2577 for (var i = 0; i <= this.npar; i++) {
2578 if (this.isQuestionMark) {
2579 switch (this.par[i]) {
2580 case 1: this.cursorKeyMode = state; break;
2581 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2582 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2583 case 6: this.offsetMode = state; break;
2584 case 7: this.autoWrapMode = state; break;
2586 case 9: this.mouseReporting = state; break;
2587 case 25: this.cursorNeedsShowing = state;
2588 if (state) { this.showCursor(); }
2589 else { this.hideCursor(); } break;
2592 case 47: this.enableAlternateScreen(state); break;
2596 switch (this.par[i]) {
2597 case 3: this.dispCtrl = state; break;
2598 case 4: this.insertMode = state; break;
2599 case 20:this.crLfMode = state; break;
2606 VT100.prototype.statusReport = function() {
2607 // Ready and operational.
2608 this.respondString += '\u001B[0n';
2611 VT100.prototype.cursorReport = function() {
2612 this.respondString += '\u001B[' +
2613 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2615 (this.cursorX + 1) +
2619 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2620 // Changing of cursor color is not implemented.
2623 VT100.prototype.csiAt = function(number) {
2628 if (number > this.terminalWidth - this.cursorX) {
2629 number = this.terminalWidth - this.cursorX;
2631 this.scrollRegion(this.cursorX, this.cursorY,
2632 this.terminalWidth - this.cursorX - number, 1,
2633 number, 0, this.style);
2634 this.needWrap = false;
2637 VT100.prototype.csiJ = function(number) {
2639 case 0: // Erase from cursor to end of display
2640 this.clearRegion(this.cursorX, this.cursorY,
2641 this.terminalWidth - this.cursorX, 1, this.style);
2642 if (this.cursorY < this.terminalHeight-2) {
2643 this.clearRegion(0, this.cursorY+1,
2644 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2648 case 1: // Erase from start to cursor
2649 if (this.cursorY > 0) {
2650 this.clearRegion(0, 0,
2651 this.terminalWidth, this.cursorY, this.style);
2653 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2655 case 2: // Erase whole display
2656 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
2664 VT100.prototype.csiK = function(number) {
2666 case 0: // Erase from cursor to end of line
2667 this.clearRegion(this.cursorX, this.cursorY,
2668 this.terminalWidth - this.cursorX, 1, this.style);
2670 case 1: // Erase from start of line to cursor
2671 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2673 case 2: // Erase whole line
2674 this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
2682 VT100.prototype.csiL = function(number) {
2683 // Open line by inserting blank line(s)
2684 if (this.cursorY >= this.bottom) {
2690 if (number > this.bottom - this.cursorY) {
2691 number = this.bottom - this.cursorY;
2693 this.scrollRegion(0, this.cursorY,
2694 this.terminalWidth, this.bottom - this.cursorY - number,
2695 0, number, this.style);
2699 VT100.prototype.csiM = function(number) {
2700 // Delete line(s), scrolling up the bottom of the screen.
2701 if (this.cursorY >= this.bottom) {
2707 if (number > this.bottom - this.cursorY) {
2708 number = bottom - cursorY;
2710 this.scrollRegion(0, this.cursorY + number,
2711 this.terminalWidth, this.bottom - this.cursorY - number,
2712 0, -number, this.style);
2716 VT100.prototype.csim = function() {
2717 for (var i = 0; i <= this.npar; i++) {
2718 switch (this.par[i]) {
2719 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2720 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2721 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2722 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2723 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2724 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
2726 this.translate = this.GMap[this.useGMap];
2727 this.dispCtrl = false;
2728 this.toggleMeta = false;
2731 this.translate = this.CodePage437Map;
2732 this.dispCtrl = true;
2733 this.toggleMeta = false;
2736 this.translate = this.CodePage437Map;
2737 this.dispCtrl = true;
2738 this.toggleMeta = true;
2741 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2742 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2743 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2744 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2745 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2746 0x0200 /* ATTR_UNDERLINE */; break;
2747 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
2748 case 49: this.attr |= 0xF0; break;
2750 if (this.par[i] >= 30 && this.par[i] <= 37) {
2751 var fg = this.par[i] - 30;
2752 this.attr = (this.attr & ~0x0F) | fg;
2753 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2754 var bg = this.par[i] - 40;
2755 this.attr = (this.attr & ~0xF0) | (bg << 4);
2763 VT100.prototype.csiP = function(number) {
2764 // Delete character(s) following cursor
2768 if (number > this.terminalWidth - this.cursorX) {
2769 number = this.terminalWidth - this.cursorX;
2771 this.scrollRegion(this.cursorX + number, this.cursorY,
2772 this.terminalWidth - this.cursorX - number, 1,
2773 -number, 0, this.style);
2777 VT100.prototype.csiX = function(number) {
2778 // Clear characters following cursor
2782 if (number > this.terminalWidth - this.cursorX) {
2783 number = this.terminalWidth - this.cursorX;
2785 this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
2789 VT100.prototype.settermCommand = function() {
2790 // Setterm commands are not implemented
2793 VT100.prototype.doControl = function(ch) {
2796 case 0x00: /* ignored */ break;
2797 case 0x08: this.bs(); break;
2798 case 0x09: this.ht(); break;
2802 case 0x84: this.lf(); if (!this.crLfMode) break;
2803 case 0x0D: this.cr(); break;
2804 case 0x85: this.cr(); this.lf(); break;
2805 case 0x0E: this.useGMap = 1;
2806 this.translate = this.GMap[1];
2807 this.dispCtrl = true; break;
2808 case 0x0F: this.useGMap = 0;
2809 this.translate = this.GMap[0];
2810 this.dispCtrl = false; break;
2812 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
2813 case 0x1B: this.isEsc = 1 /* ESesc */; break;
2814 case 0x7F: /* ignored */ break;
2815 case 0x88: this.userTabStop[this.cursorX] = true; break;
2816 case 0x8D: this.ri(); break;
2817 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
2818 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
2819 case 0x9A: this.respondID(); break;
2820 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
2821 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
2825 default: switch (this.isEsc) {
2827 this.isEsc = 0 /* ESnormal */;
2829 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
2830 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
2832 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
2834 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
2836 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
2837 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
2838 /*7*/ case 0x37: this.saveCursor(); break;
2839 /*8*/ case 0x38: this.restoreCursor(); break;
2840 /*>*/ case 0x3E: this.applKeyMode = false; break;
2841 /*=*/ case 0x3D: this.applKeyMode = true; break;
2842 /*D*/ case 0x44: this.lf(); break;
2843 /*E*/ case 0x45: this.cr(); this.lf(); break;
2844 /*M*/ case 0x4D: this.ri(); break;
2845 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
2846 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
2847 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
2848 /*Z*/ case 0x5A: this.respondID(); break;
2849 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
2850 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
2851 /*c*/ case 0x63: this.reset(); break;
2852 /*g*/ case 0x67: this.flashScreen(); break;
2856 case 15 /* ESnonstd */:
2860 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
2861 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
2862 this.isEsc = 16 /* ESpalette */; break;
2863 /*R*/ case 0x52: // Palette support is not implemented
2864 this.isEsc = 0 /* ESnormal */; break;
2865 default: this.isEsc = 0 /* ESnormal */; break;
2868 case 16 /* ESpalette */:
2869 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
2870 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
2871 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
2872 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
2874 if (this.npar == 7) {
2875 // Palette support is not implemented
2876 this.isEsc = 0 /* ESnormal */;
2879 this.isEsc = 0 /* ESnormal */;
2882 case 2 /* ESsquare */:
2884 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
2885 0, 0, 0, 0, 0, 0, 0, 0 ];
2886 this.isEsc = 3 /* ESgetpars */;
2887 /*[*/ if (ch == 0x5B) { // Function key
2888 this.isEsc = 6 /* ESfunckey */;
2891 /*?*/ this.isQuestionMark = ch == 0x3F;
2892 if (this.isQuestionMark) {
2897 case 5 /* ESdeviceattr */:
2898 case 3 /* ESgetpars */:
2899 /*;*/ if (ch == 0x3B) {
2902 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
2903 var par = this.par[this.npar];
2904 if (par == undefined) {
2907 this.par[this.npar] = 10*par + (ch & 0xF);
2909 } else if (this.isEsc == 5 /* ESdeviceattr */) {
2911 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
2912 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
2913 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
2914 /*p*/ case 0x70: /* set pointer mode resource value */ break;
2917 this.isEsc = 0 /* ESnormal */;
2920 this.isEsc = 4 /* ESgotpars */;
2923 case 4 /* ESgotpars */:
2924 this.isEsc = 0 /* ESnormal */;
2925 if (this.isQuestionMark) {
2927 /*h*/ case 0x68: this.setMode(true); break;
2928 /*l*/ case 0x6C: this.setMode(false); break;
2929 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
2932 this.isQuestionMark = false;
2936 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
2937 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
2939 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
2940 /*A*/ case 0x41: this.gotoXY(this.cursorX,
2941 this.cursorY - (this.par[0] ? this.par[0] : 1));
2944 /*e*/ case 0x65: this.gotoXY(this.cursorX,
2945 this.cursorY + (this.par[0] ? this.par[0] : 1));
2948 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
2949 this.cursorY); break;
2950 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
2951 this.cursorY); break;
2952 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
2954 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
2956 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
2958 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
2959 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
2960 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
2961 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
2962 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
2963 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
2964 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
2965 /*m*/ case 0x6D: this.csim(); break;
2966 /*P*/ case 0x50: this.csiP(this.par[0]); break;
2967 /*X*/ case 0x58: this.csiX(this.par[0]); break;
2968 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
2969 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
2970 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
2971 /*g*/ case 0x67: if (this.par[0] == 0) {
2972 this.userTabStop[this.cursorX] = false;
2973 } else if (this.par[0] == 2 || this.par[0] == 3) {
2974 this.userTabStop = [ ];
2975 for (var i = 0; i < this.terminalWidth; i++) {
2976 this.userTabStop[i] = false;
2980 /*h*/ case 0x68: this.setMode(true); break;
2981 /*l*/ case 0x6C: this.setMode(false); break;
2982 /*n*/ case 0x6E: switch (this.par[0]) {
2983 case 5: this.statusReport(); break;
2984 case 6: this.cursorReport(); break;
2988 /*q*/ case 0x71: // LED control not implemented
2990 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
2991 var b = this.par[1] ? this.par[1]
2992 : this.terminalHeight;
2993 if (t < b && b <= this.terminalHeight) {
2999 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3000 if (c > this.terminalWidth * this.terminalHeight) {
3001 c = this.terminalWidth * this.terminalHeight;
3004 lineBuf += this.lastCharacter;
3007 /*s*/ case 0x73: this.saveCursor(); break;
3008 /*u*/ case 0x75: this.restoreCursor(); break;
3009 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3010 /*]*/ case 0x5D: this.settermCommand(); break;
3014 case 12 /* ESbang */:
3018 this.isEsc = 0 /* ESnormal */;
3020 case 13 /* ESpercent */:
3021 this.isEsc = 0 /* ESnormal */;
3023 /*@*/ case 0x40: this.utfEnabled = false; break;
3025 /*8*/ case 0x38: this.utfEnabled = true; break;
3029 case 6 /* ESfunckey */:
3030 this.isEsc = 0 /* ESnormal */; break;
3031 case 7 /* EShash */:
3032 this.isEsc = 0 /* ESnormal */;
3033 /*8*/ if (ch == 0x38) {
3034 // Screen alignment test not implemented
3037 case 8 /* ESsetG0 */:
3038 case 9 /* ESsetG1 */:
3039 case 10 /* ESsetG2 */:
3040 case 11 /* ESsetG3 */:
3041 var g = this.isEsc - 8 /* ESsetG0 */;
3042 this.isEsc = 0 /* ESnormal */;
3044 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3046 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3047 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3048 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3051 if (this.useGMap == g) {
3052 this.translate = this.GMap[g];
3055 case 17 /* ESstatus */:
3057 if (this.statusString && this.statusString.charAt(0) == ';') {
3058 this.statusString = this.statusString.substr(1);
3061 window.status = this.statusString;
3064 this.isEsc = 0 /* ESnormal */;
3066 this.statusString += String.fromCharCode(ch);
3069 case 18 /* ESss2 */:
3070 case 19 /* ESss3 */:
3072 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3073 [this.toggleMeta ? (ch | 0x80) : ch];
3074 if ((ch & 0xFF00) == 0xF000) {
3076 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3077 this.isEsc = 0 /* ESnormal */; break;
3080 this.lastCharacter = String.fromCharCode(ch);
3081 lineBuf += this.lastCharacter;
3082 this.isEsc = 0 /* ESnormal */; break;
3084 this.isEsc = 0 /* ESnormal */; break;
3091 VT100.prototype.renderString = function(s, showCursor) {
3092 // We try to minimize the number of DOM operations by coalescing individual
3093 // characters into strings. This is a significant performance improvement.
3094 var incX = s.length;
3095 if (incX > this.terminalWidth - this.cursorX) {
3096 incX = this.terminalWidth - this.cursorX;
3100 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3103 // Minimize the number of calls to putString(), by avoiding a direct
3104 // call to this.showCursor()
3105 this.cursor.style.visibility = '';
3107 this.putString(this.cursorX, this.cursorY, s, this.style);
3110 VT100.prototype.vt100 = function(s) {
3111 this.cursorNeedsShowing = this.hideCursor();
3112 this.respondString = '';
3114 for (var i = 0; i < s.length; i++) {
3115 var ch = s.charCodeAt(i);
3116 if (this.utfEnabled) {
3117 // Decode UTF8 encoded character
3119 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3120 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3121 if (--this.utfCount <= 0) {
3122 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3131 if ((ch & 0xE0) == 0xC0) {
3133 this.utfChar = ch & 0x1F;
3134 } else if ((ch & 0xF0) == 0xE0) {
3136 this.utfChar = ch & 0x0F;
3137 } else if ((ch & 0xF8) == 0xF0) {
3139 this.utfChar = ch & 0x07;
3140 } else if ((ch & 0xFC) == 0xF8) {
3142 this.utfChar = ch & 0x03;
3143 } else if ((ch & 0xFE) == 0xFC) {
3145 this.utfChar = ch & 0x01;
3155 var isNormalCharacter =
3156 (ch >= 32 && ch <= 127 || ch >= 160 ||
3157 this.utfEnabled && ch >= 128 ||
3158 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3159 (ch != 0x7F || this.dispCtrl);
3161 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3163 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3165 if ((ch & 0xFF00) == 0xF000) {
3167 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3170 if (this.needWrap || this.insertMode) {
3172 this.renderString(lineBuf);
3176 if (this.needWrap) {
3177 this.cr(); this.lf();
3179 if (this.insertMode) {
3180 this.scrollRegion(this.cursorX, this.cursorY,
3181 this.terminalWidth - this.cursorX - 1, 1,
3184 this.lastCharacter = String.fromCharCode(ch);
3185 lineBuf += this.lastCharacter;
3186 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3187 this.needWrap = this.autoWrapMode;
3191 this.renderString(lineBuf);
3194 var expand = this.doControl(ch);
3195 if (expand.length) {
3196 var r = this.respondString;
3197 this.respondString= r + this.vt100(expand);
3202 this.renderString(lineBuf, this.cursorNeedsShowing);
3203 } else if (this.cursorNeedsShowing) {
3206 return this.respondString;
3209 VT100.prototype.Latin1Map = [
3210 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3211 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3212 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3213 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3214 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3215 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3216 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3217 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3218 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3219 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3220 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3221 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3222 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3223 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3224 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3225 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3226 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3227 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3228 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3229 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3230 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3231 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3232 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3233 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3234 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3235 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3236 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3237 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3238 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3239 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3240 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3241 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3244 VT100.prototype.VT100GraphicsMap = [
3245 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3246 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3247 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3248 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3249 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3250 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3251 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3252 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3253 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3254 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3255 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3256 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3257 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3258 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3259 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3260 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3261 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3262 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3263 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3264 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3265 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3266 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3267 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3268 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3269 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3270 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3271 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3272 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3273 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3274 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3275 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3276 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3279 VT100.prototype.CodePage437Map = [
3280 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3281 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3282 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3283 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3284 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3285 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3286 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3287 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3288 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3289 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3290 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3291 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3292 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3293 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3294 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3295 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3296 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3297 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3298 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3299 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3300 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3301 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3302 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3303 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3304 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3305 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3306 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3307 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3308 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3309 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3310 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3311 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3314 VT100.prototype.DirectToFontMap = [
3315 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3316 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3317 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3318 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3319 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3320 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3321 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3322 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3323 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3324 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3325 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3326 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3327 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3328 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3329 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3330 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3331 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3332 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3333 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3334 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3335 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3336 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3337 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3338 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3339 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3340 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3341 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3342 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3343 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3344 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3345 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3346 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3349 VT100.prototype.ctrlAction = [
3350 true, false, false, false, false, false, false, true,
3351 true, true, true, true, true, true, true, true,
3352 false, false, false, false, false, false, false, false,
3353 true, false, true, true, false, false, false, false
3356 VT100.prototype.ctrlAlways = [
3357 true, false, false, false, false, false, false, false,
3358 true, false, true, false, true, true, true, true,
3359 false, false, false, false, false, false, false, false,
3360 false, false, false, true, false, false, false, false