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