1 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com> All rights reserved.
3 // SPDX-License-Identifier: GPL-2.0
5 // This file contains code from shell_in_a_box.js and vt100.js
8 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
9 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License version 2 as
13 // published by the Free Software Foundation.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License along
21 // with this program; if not, write to the Free Software Foundation, Inc.,
22 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 // In addition to these license terms, the author grants the following
27 // If you modify this program, or any covered work, by linking or
28 // combining it with the OpenSSL project's OpenSSL library (or a
29 // modified version of that library), containing parts covered by the
30 // terms of the OpenSSL or SSLeay licenses, the author
31 // grants you additional permission to convey the resulting work.
32 // Corresponding Source for a non-source form of such a combination
33 // shall include the source code for the parts of OpenSSL used as well
34 // as that of the covered work.
36 // You may at your option choose to remove this additional permission from
37 // the work, or from any part of it.
39 // It is possible to build this program in a way that it loads OpenSSL
40 // libraries at run-time. If doing so, the following notices are required
41 // by the OpenSSL and SSLeay licenses:
43 // This product includes software developed by the OpenSSL Project
44 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
46 // This product includes cryptographic software written by Eric Young
47 // (eay@cryptsoft.com)
50 // The most up-to-date version of this program is always available from
51 // http://shellinabox.com
56 // The author believes that for the purposes of this license, you meet the
57 // requirements for publishing the source code, if your web server publishes
58 // the source in unmodified form (i.e. with licensing information, comments,
59 // formatting, and identifier names intact). If there are technical reasons
60 // that require you to make changes to the source code when serving the
61 // JavaScript (e.g to remove pre-processor directives from the source), these
62 // changes should be done in a reversible fashion.
64 // The author does not consider websites that reference this script in
65 // unmodified form, and web servers that serve this script in unmodified form
66 // to be derived works. As such, they are believed to be outside of the
67 // scope of this license and not subject to the rights or restrictions of the
68 // GNU General Public License.
70 // If in doubt, consult a legal professional familiar with the laws that
71 // apply in your country.
73 // #define XHR_UNITIALIZED 0
76 // #define XHR_RECEIVING 3
77 // #define XHR_LOADED 4
79 // IE does not define XMLHttpRequest by default, so we provide a suitable
81 if (typeof XMLHttpRequest == 'undefined') {
82 XMLHttpRequest = function() {
83 try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
84 try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
85 try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { }
86 try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
91 function extend(subClass, baseClass) {
92 function inheritance() { }
93 inheritance.prototype = baseClass.prototype;
94 subClass.prototype = new inheritance();
95 subClass.prototype.constructor = subClass;
96 subClass.prototype.superClass = baseClass.prototype;
99 function ShellInABox(url, container) {
100 if (url == undefined) {
101 this.rooturl = document.location.href;
102 this.url = document.location.href.replace(/[?#].*/, '');
107 if (document.location.hash != '') {
108 var hash = decodeURIComponent(document.location.hash).
110 this.nextUrl = hash.replace(/,.*/, '');
111 this.session = hash.replace(/[^,]*,/, '');
113 this.nextUrl = this.url;
116 this.pendingKeys = '';
117 this.keysInFlight = false;
118 this.connected = false;
119 this.superClass.constructor.call(this, container);
121 // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
122 // Chrome never realizes that the page has loaded.
123 setTimeout(function(shellInABox) {
125 shellInABox.sendRequest();
129 extend(ShellInABox, VT100);
131 ShellInABox.prototype.sessionClosed = function() {
133 this.connected = false;
135 this.session = undefined;
136 if (this.cursorX > 0) {
139 this.vt100('Session closed.');
141 // Revealing the "reconnect" button is commented out until we hook
142 // up the username+token auto-login mechanism to the new session:
143 //this.showReconnect(true);
148 ShellInABox.prototype.reconnect = function() {
149 this.showReconnect(false);
151 if (document.location.hash != '') {
152 // A shellinaboxd daemon launched from a CGI only allows a single
153 // session. In order to reconnect, we must reload the frame definition
154 // and obtain a new port number. As this is a different origin, we
155 // need to get enclosing page to help us.
156 parent.location = this.nextUrl;
158 if (this.url != this.nextUrl) {
159 document.location.replace(this.nextUrl);
161 this.pendingKeys = '';
162 this.keysInFlight = false;
171 ShellInABox.prototype.sendRequest = function(request) {
172 if (request == undefined) {
173 request = new XMLHttpRequest();
175 request.open('POST', this.url + '?', true);
176 request.setRequestHeader('Cache-Control', 'no-cache');
177 request.setRequestHeader('Content-Type',
178 'application/x-www-form-urlencoded; charset=utf-8');
179 var content = 'width=' + this.terminalWidth +
180 '&height=' + this.terminalHeight +
181 (this.session ? '&session=' +
182 encodeURIComponent(this.session) : '&rooturl='+
183 encodeURIComponent(this.rooturl));
185 request.onreadystatechange = function(shellInABox) {
188 return shellInABox.onReadyStateChange(request);
190 shellInABox.sessionClosed();
194 ShellInABox.lastRequestSent = Date.now();
195 request.send(content);
198 ShellInABox.prototype.onReadyStateChange = function(request) {
199 if (request.readyState == 4 /* XHR_LOADED */) {
200 if (request.status == 200) {
201 this.connected = true;
202 var response = eval('(' + request.responseText + ')');
204 this.vt100(response.data);
207 if (!response.session ||
208 this.session && this.session != response.session) {
209 this.sessionClosed();
211 this.session = response.session;
212 this.sendRequest(request);
214 } else if (request.status == 0) {
215 if (ShellInABox.lastRequestSent + 2000 < Date.now()) {
216 // Timeout, try again
217 this.sendRequest(request);
219 this.vt100('\r\n\r\nRequest failed.');
220 this.sessionClosed();
223 this.sessionClosed();
228 ShellInABox.prototype.sendKeys = function(keys) {
229 if (!this.connected) {
232 if (this.keysInFlight || this.session == undefined) {
233 this.pendingKeys += keys;
235 this.keysInFlight = true;
236 keys = this.pendingKeys + keys;
237 this.pendingKeys = '';
238 var request = new XMLHttpRequest();
239 request.open('POST', this.url + '?', true);
240 request.setRequestHeader('Cache-Control', 'no-cache');
241 request.setRequestHeader('Content-Type',
242 'application/x-www-form-urlencoded; charset=utf-8');
243 var content = 'width=' + this.terminalWidth +
244 '&height=' + this.terminalHeight +
245 '&session=' +encodeURIComponent(this.session)+
246 '&keys=' + encodeURIComponent(keys);
247 request.onreadystatechange = function(shellInABox) {
250 return shellInABox.keyPressReadyStateChange(request);
255 request.send(content);
259 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
260 if (request.readyState == 4 /* XHR_LOADED */) {
261 this.keysInFlight = false;
262 if (this.pendingKeys) {
268 ShellInABox.prototype.keysPressed = function(ch) {
269 var hex = '0123456789ABCDEF';
271 for (var i = 0; i < ch.length; i++) {
272 var c = ch.charCodeAt(i);
274 s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
275 } else if (c < 0x800) {
276 s += hex.charAt(0xC + (c >> 10) ) +
277 hex.charAt( (c >> 6) & 0xF ) +
278 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
279 hex.charAt( c & 0xF );
280 } else if (c < 0x10000) {
282 hex.charAt( (c >> 12) ) +
283 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
284 hex.charAt( (c >> 6) & 0xF ) +
285 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
286 hex.charAt( c & 0xF );
287 } else if (c < 0x110000) {
289 hex.charAt( (c >> 18) ) +
290 hex.charAt(0x8 + ((c >> 16) & 0x3)) +
291 hex.charAt( (c >> 12) & 0xF ) +
292 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
293 hex.charAt( (c >> 6) & 0xF ) +
294 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
295 hex.charAt( c & 0xF );
301 ShellInABox.prototype.resized = function(w, h) {
302 // Do not send a resize request until we are fully initialized.
304 // sendKeys() always transmits the current terminal size. So, flush all
310 ShellInABox.prototype.toggleSSL = function() {
311 if (document.location.hash != '') {
312 if (this.nextUrl.match(/\?plain$/)) {
313 this.nextUrl = this.nextUrl.replace(/\?plain$/, '');
315 this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain';
318 parent.location = this.nextUrl;
321 this.nextUrl = this.nextUrl.match(/^https:/)
322 ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
323 : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
325 if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
328 if (this.session && this.nextUrl != this.url) {
329 alert('This change will take effect the next time you login.');
333 ShellInABox.prototype.extendContextMenu = function(entries, actions) {
334 // Modify the entries and actions in place, adding any locally defined
336 var oldActions = [ ];
337 for (var i = 0; i < actions.length; i++) {
338 oldActions[i] = actions[i];
340 for (var node = entries.firstChild, i = 0, j = 0; node;
341 node = node.nextSibling) {
342 if (node.tagName == 'LI') {
343 actions[i++] = oldActions[j++];
344 if (node.id == "endconfig") {
346 if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
347 !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
348 // If the server supports both SSL and plain text connections,
349 // provide a menu entry to switch between the two.
350 var newNode = document.createElement('li');
352 if (document.location.hash != '') {
353 isSecure = !this.nextUrl.match(/\?plain$/);
355 isSecure = this.nextUrl.match(/^https:/);
357 newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
358 if (node.nextSibling) {
359 entries.insertBefore(newNode, node.nextSibling);
361 entries.appendChild(newNode);
363 actions[i++] = this.toggleSSL;
366 node.id = 'endconfig';
373 ShellInABox.prototype.about = function() {
374 alert("Shell In A Box version " + "2.10 (revision 239)" +
375 "\nCopyright 2008-2010 by Markus Gutschke\n" +
376 "For more information check http://shellinabox.com" +
377 (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
379 "This product includes software developed by the OpenSSL Project\n" +
380 "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" +
382 "This product includes cryptographic software written by " +
383 "Eric Young\n(eay@cryptsoft.com)" :
388 // VT100.js -- JavaScript based terminal emulator
389 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
391 // This program is free software; you can redistribute it and/or modify
392 // it under the terms of the GNU General Public License version 2 as
393 // published by the Free Software Foundation.
395 // This program is distributed in the hope that it will be useful,
396 // but WITHOUT ANY WARRANTY; without even the implied warranty of
397 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
398 // GNU General Public License for more details.
400 // You should have received a copy of the GNU General Public License along
401 // with this program; if not, write to the Free Software Foundation, Inc.,
402 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
404 // In addition to these license terms, the author grants the following
405 // additional rights:
407 // If you modify this program, or any covered work, by linking or
408 // combining it with the OpenSSL project's OpenSSL library (or a
409 // modified version of that library), containing parts covered by the
410 // terms of the OpenSSL or SSLeay licenses, the author
411 // grants you additional permission to convey the resulting work.
412 // Corresponding Source for a non-source form of such a combination
413 // shall include the source code for the parts of OpenSSL used as well
414 // as that of the covered work.
416 // You may at your option choose to remove this additional permission from
417 // the work, or from any part of it.
419 // It is possible to build this program in a way that it loads OpenSSL
420 // libraries at run-time. If doing so, the following notices are required
421 // by the OpenSSL and SSLeay licenses:
423 // This product includes software developed by the OpenSSL Project
424 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
426 // This product includes cryptographic software written by Eric Young
427 // (eay@cryptsoft.com)
430 // The most up-to-date version of this program is always available from
431 // http://shellinabox.com
436 // The author believes that for the purposes of this license, you meet the
437 // requirements for publishing the source code, if your web server publishes
438 // the source in unmodified form (i.e. with licensing information, comments,
439 // formatting, and identifier names intact). If there are technical reasons
440 // that require you to make changes to the source code when serving the
441 // JavaScript (e.g to remove pre-processor directives from the source), these
442 // changes should be done in a reversible fashion.
444 // The author does not consider websites that reference this script in
445 // unmodified form, and web servers that serve this script in unmodified form
446 // to be derived works. As such, they are believed to be outside of the
447 // scope of this license and not subject to the rights or restrictions of the
448 // GNU General Public License.
450 // If in doubt, consult a legal professional familiar with the laws that
451 // apply in your country.
453 // #define ESnormal 0
455 // #define ESsquare 2
456 // #define ESgetpars 3
457 // #define ESgotpars 4
458 // #define ESdeviceattr 5
459 // #define ESfunckey 6
463 // #define ESsetG2 10
464 // #define ESsetG3 11
466 // #define ESpercent 13
467 // #define ESignore 14
468 // #define ESnonstd 15
469 // #define ESpalette 16
470 // #define EStitle 17
474 // #define ATTR_DEFAULT 0x00F0
475 // #define ATTR_REVERSE 0x0100
476 // #define ATTR_UNDERLINE 0x0200
477 // #define ATTR_DIM 0x0400
478 // #define ATTR_BRIGHT 0x0800
479 // #define ATTR_BLINK 0x1000
481 // #define MOUSE_DOWN 0
482 // #define MOUSE_UP 1
483 // #define MOUSE_CLICK 2
485 function VT100(container) {
486 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
489 this.urlRE = new RegExp(
490 // Known URL protocol are "http", "https", and "ftp".
491 '(?:http|https|ftp)://' +
493 // Optionally allow username and passwords.
494 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
497 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
498 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
499 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
502 '(?::[1-9][0-9]*)?' +
505 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
507 (linkifyURLs <= 1 ? '' :
508 // Also support URLs without a protocol (assume "http").
509 // Optional username and password.
510 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
512 // Hostnames must end with a well-known top-level domain or must be
514 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
517 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
518 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
519 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
520 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
521 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
522 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
523 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
524 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
525 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
526 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
527 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
528 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
529 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
530 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
533 '(?::[1-9][0-9]{0,4})?' +
536 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
538 // In addition, support e-mail address. Optionally, recognize "mailto:"
539 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
542 '[-_.+a-zA-Z0-9]+@' +
545 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
546 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
547 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
548 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
549 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
550 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
551 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
552 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
553 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
554 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
555 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
556 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
557 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
558 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
560 // Optional arguments
561 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
563 this.getUserSettings();
564 this.initializeElements(container);
565 this.maxScrollbackLines = 500;
568 this.isQuestionMark = false;
571 this.savedAttr = [ ];
572 this.savedUseGMap = 0;
573 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
574 this.CodePage437Map, this.DirectToFontMap ];
575 this.savedValid = [ ];
576 this.respondString = '';
577 this.titleString = '';
578 this.internalClipboard = undefined;
582 VT100.prototype.reset = function(clearHistory) {
583 this.isEsc = 0 /* ESnormal */;
584 this.needWrap = false;
585 this.autoWrapMode = true;
586 this.dispCtrl = false;
587 this.toggleMeta = false;
588 this.insertMode = false;
589 this.applKeyMode = false;
590 this.cursorKeyMode = false;
591 this.crLfMode = false;
592 this.offsetMode = false;
593 this.mouseReporting = false;
594 this.printing = false;
595 if (typeof this.printWin != 'undefined' &&
596 this.printWin && !this.printWin.closed) {
597 this.printWin.close();
599 this.printWin = null;
600 this.utfEnabled = this.utfPreferred;
603 this.color = 'ansi0 bgAnsi15';
605 this.attr = 0x00F0 /* ATTR_DEFAULT */;
607 this.GMap = [ this.Latin1Map,
608 this.VT100GraphicsMap,
610 this.DirectToFontMap];
611 this.translate = this.GMap[this.useGMap];
613 this.bottom = this.terminalHeight;
614 this.lastCharacter = ' ';
615 this.userTabStop = [ ];
618 for (var i = 0; i < 2; i++) {
619 while (this.console[i].firstChild) {
620 this.console[i].removeChild(this.console[i].firstChild);
625 this.enableAlternateScreen(false);
627 var wasCompressed = false;
628 var transform = this.getTransformName();
630 for (var i = 0; i < 2; ++i) {
631 wasCompressed |= this.console[i].style[transform] != '';
632 this.console[i].style[transform] = '';
634 this.cursor.style[transform] = '';
635 this.space.style[transform] = '';
636 if (transform == 'filter') {
637 this.console[this.currentScreen].style.width = '';
647 this.isInverted = false;
648 this.refreshInvertedState();
649 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
650 this.color, this.style);
653 VT100.prototype.addListener = function(elem, event, listener) {
655 if (elem.addEventListener) {
656 elem.addEventListener(event, listener, false);
658 elem.attachEvent('on' + event, listener);
664 VT100.prototype.getUserSettings = function() {
665 // Compute hash signature to identify the entries in the userCSS menu.
666 // If the menu is unchanged from last time, default values can be
667 // looked up in a cookie associated with this page.
669 this.utfPreferred = true;
670 this.visualBell = typeof suppressAllAudio != 'undefined' &&
672 this.autoprint = true;
673 this.softKeyboard = false;
674 this.blinkingCursor = true;
675 if (this.visualBell) {
676 this.signature = Math.floor(16807*this.signature + 1) %
679 if (typeof userCSSList != 'undefined') {
680 for (var i = 0; i < userCSSList.length; ++i) {
681 var label = userCSSList[i][0];
682 for (var j = 0; j < label.length; ++j) {
683 this.signature = Math.floor(16807*this.signature+
684 label.charCodeAt(j)) %
687 if (userCSSList[i][1]) {
688 this.signature = Math.floor(16807*this.signature + 1) %
694 var key = 'shellInABox=' + this.signature + ':';
695 var settings = document.cookie.indexOf(key);
697 settings = document.cookie.substr(settings + key.length).
698 replace(/([0-1]*).*/, "$1");
699 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
700 0 : userCSSList.length)) {
701 this.utfPreferred = settings.charAt(0) != '0';
702 this.visualBell = settings.charAt(1) != '0';
703 this.autoprint = settings.charAt(2) != '0';
704 this.softKeyboard = settings.charAt(3) != '0';
705 this.blinkingCursor = settings.charAt(4) != '0';
706 if (typeof userCSSList != 'undefined') {
707 for (var i = 0; i < userCSSList.length; ++i) {
708 userCSSList[i][2] = settings.charAt(i + 5) != '0';
713 this.utfEnabled = this.utfPreferred;
716 VT100.prototype.storeUserSettings = function() {
717 var settings = 'shellInABox=' + this.signature + ':' +
718 (this.utfEnabled ? '1' : '0') +
719 (this.visualBell ? '1' : '0') +
720 (this.autoprint ? '1' : '0') +
721 (this.softKeyboard ? '1' : '0') +
722 (this.blinkingCursor ? '1' : '0');
723 if (typeof userCSSList != 'undefined') {
724 for (var i = 0; i < userCSSList.length; ++i) {
725 settings += userCSSList[i][2] ? '1' : '0';
729 d.setDate(d.getDate() + 3653);
730 document.cookie = settings + ';expires=' + d.toGMTString();
733 VT100.prototype.initializeUserCSSStyles = function() {
734 this.usercssActions = [];
735 if (typeof userCSSList != 'undefined') {
738 var wasSingleSel = 1;
739 var beginOfGroup = 0;
740 for (var i = 0; i <= userCSSList.length; ++i) {
741 if (i < userCSSList.length) {
742 var label = userCSSList[i][0];
743 var newGroup = userCSSList[i][1];
744 var enabled = userCSSList[i][2];
746 // Add user style sheet to document
747 var style = document.createElement('link');
748 var id = document.createAttribute('id');
749 id.nodeValue = 'usercss-' + i;
750 style.setAttributeNode(id);
751 var rel = document.createAttribute('rel');
752 rel.nodeValue = 'stylesheet';
753 style.setAttributeNode(rel);
754 var href = document.createAttribute('href');
755 href.nodeValue = 'usercss-' + i + '.css';
756 style.setAttributeNode(href);
757 var type = document.createAttribute('type');
758 type.nodeValue = 'text/css';
759 style.setAttributeNode(type);
760 document.getElementsByTagName('head')[0].appendChild(style);
761 style.disabled = !enabled;
765 if (newGroup || i == userCSSList.length) {
766 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
767 // The last group had multiple entries that are mutually exclusive;
768 // or the previous to last group did. In either case, we need to
769 // append a "<hr />" before we can add the last group to the menu.
772 wasSingleSel = i - beginOfGroup < 1;
776 for (var j = beginOfGroup; j < i; ++j) {
777 this.usercssActions[this.usercssActions.length] =
778 function(vt100, current, begin, count) {
780 // Deselect all other entries in the group, then either select
781 // (for multiple entries in group) or toggle (for on/off entry)
782 // the current entry.
784 var entry = vt100.getChildById(vt100.menu,
788 for (var c = count; c > 0; ++j) {
789 if (entry.tagName == 'LI') {
792 var label = vt100.usercss.childNodes[j];
794 // Restore label to just the text content
795 if (typeof label.textContent == 'undefined') {
796 var s = label.innerText;
797 label.innerHTML = '';
798 label.appendChild(document.createTextNode(s));
800 label.textContent= label.textContent;
803 // User style sheets are numbered sequentially
804 var sheet = document.getElementById(
808 sheet.disabled = !sheet.disabled;
810 sheet.disabled = false;
812 if (!sheet.disabled) {
813 label.innerHTML= '<img src="/webshell/enabled.gif" />' +
817 sheet.disabled = true;
819 userCSSList[i][2] = !sheet.disabled;
822 entry = entry.nextSibling;
825 // If the font size changed, adjust cursor and line dimensions
826 this.cursor.style.cssText= '';
827 this.cursorWidth = this.cursor.clientWidth;
828 this.cursorHeight = this.lineheight.clientHeight;
829 for (i = 0; i < this.console.length; ++i) {
830 for (var line = this.console[i].firstChild; line;
831 line = line.nextSibling) {
832 line.style.height = this.cursorHeight + 'px';
837 }(this, j, beginOfGroup, i - beginOfGroup);
840 if (i == userCSSList.length) {
846 // Collect all entries in a group, before attaching them to the menu.
847 // This is necessary as we don't know whether this is a group of
848 // mutually exclusive options (which should be separated by "<hr />" on
849 // both ends), or whether this is a on/off toggle, which can be grouped
850 // together with other on/off options.
852 '<li>' + (enabled ? '<img src="/webshell/enabled.gif" />' : '') +
856 this.usercss.innerHTML = menu;
860 VT100.prototype.resetLastSelectedKey = function(e) {
861 var key = this.lastSelectedKey;
866 var position = this.mousePosition(e);
868 // We don't get all the necessary events to reliably reselect a key
869 // if we moved away from it and then back onto it. We approximate the
870 // behavior by remembering the key until either we release the mouse
871 // button (we might never get this event if the mouse has since left
872 // the window), or until we move away too far.
873 var box = this.keyboard.firstChild;
874 if (position[0] < box.offsetLeft + key.offsetWidth ||
875 position[1] < box.offsetTop + key.offsetHeight ||
876 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
877 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
878 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
879 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
880 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
881 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
882 if (this.lastSelectedKey.className) log.console('reset: deselecting');
883 this.lastSelectedKey.className = '';
884 this.lastSelectedKey = undefined;
889 VT100.prototype.showShiftState = function(state) {
890 var style = document.getElementById('shift_state');
892 this.setTextContentRaw(style,
893 '#vt100 #keyboard .shifted {' +
894 'display: inline }' +
895 '#vt100 #keyboard .unshifted {' +
898 this.setTextContentRaw(style, '');
900 var elems = this.keyboard.getElementsByTagName('I');
901 for (var i = 0; i < elems.length; ++i) {
902 if (elems[i].id == '16') {
903 elems[i].className = state ? 'selected' : '';
908 VT100.prototype.showCtrlState = function(state) {
909 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
911 ctrl.className = state ? 'selected' : '';
915 VT100.prototype.showAltState = function(state) {
916 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
918 alt.className = state ? 'selected' : '';
922 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
927 fake.shiftKey = shift;
930 return this.handleKey(fake);
933 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
934 if (elem == undefined) {
937 if (ch == '\u00A0') {
938 // should be treated as a regular space character.
941 if (ch != undefined && CH == undefined) {
942 // For letter keys, we automatically compute the uppercase character code
943 // from the lowercase one.
944 CH = ch.toUpperCase();
946 if (KEY == undefined && key != undefined) {
947 // Most keys have identically key codes for both lowercase and uppercase
948 // keypresses. Normally, only function keys would have distinct key codes,
949 // whereas regular keys have character codes.
951 } else if (KEY == undefined && CH != undefined) {
952 // For regular keys, copy the character code to the key code.
953 KEY = CH.charCodeAt(0);
955 if (key == undefined && ch != undefined) {
956 // For regular keys, copy the character code to the key code.
957 key = ch.charCodeAt(0);
959 // Convert characters to numeric character codes. If the character code
960 // is undefined (i.e. this is a function key), set it to zero.
961 ch = ch ? ch.charCodeAt(0) : 0;
962 CH = CH ? CH.charCodeAt(0) : 0;
964 // Mouse down events high light the key. We also set lastSelectedKey. This
965 // is needed to that mouseout/mouseover can keep track of the key that
966 // is currently being clicked.
967 this.addListener(elem, 'mousedown',
968 function(vt100, elem, key) { return function(e) {
969 if ((e.which || e.button) == 1) {
970 if (vt100.lastSelectedKey) {
971 vt100.lastSelectedKey.className= '';
973 // Highlight the key while the mouse button is held down.
974 if (key == 16 /* Shift */) {
975 if (!elem.className != vt100.isShift) {
976 vt100.showShiftState(!vt100.isShift);
978 } else if (key == 17 /* Ctrl */) {
979 if (!elem.className != vt100.isCtrl) {
980 vt100.showCtrlState(!vt100.isCtrl);
982 } else if (key == 18 /* Alt */) {
983 if (!elem.className != vt100.isAlt) {
984 vt100.showAltState(!vt100.isAlt);
987 elem.className = 'selected';
989 vt100.lastSelectedKey = elem;
991 return false; }; }(this, elem, key));
993 // Modifier keys update the state of the keyboard, but do not generate
994 // any key clicks that get forwarded to the application.
995 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
996 function(vt100, elem) { return function(e) {
997 if (elem == vt100.lastSelectedKey) {
998 if (key == 16 /* Shift */) {
999 // The user clicked the Shift key
1000 vt100.isShift = !vt100.isShift;
1001 vt100.showShiftState(vt100.isShift);
1002 } else if (key == 17 /* Ctrl */) {
1003 vt100.isCtrl = !vt100.isCtrl;
1004 vt100.showCtrlState(vt100.isCtrl);
1005 } else if (key == 18 /* Alt */) {
1006 vt100.isAlt = !vt100.isAlt;
1007 vt100.showAltState(vt100.isAlt);
1009 vt100.lastSelectedKey = undefined;
1011 if (vt100.lastSelectedKey) {
1012 vt100.lastSelectedKey.className = '';
1013 vt100.lastSelectedKey = undefined;
1015 return false; }; }(this, elem) :
1016 // Regular keys generate key clicks, when the mouse button is released or
1017 // when a mouse click event is received.
1018 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
1019 if (vt100.lastSelectedKey) {
1020 if (elem == vt100.lastSelectedKey) {
1021 // The user clicked a key.
1022 if (vt100.isShift) {
1023 vt100.clickedKeyboard(e, elem, CH, KEY,
1024 true, vt100.isCtrl, vt100.isAlt);
1026 vt100.clickedKeyboard(e, elem, ch, key,
1027 false, vt100.isCtrl, vt100.isAlt);
1029 vt100.isShift = false;
1030 vt100.showShiftState(false);
1031 vt100.isCtrl = false;
1032 vt100.showCtrlState(false);
1033 vt100.isAlt = false;
1034 vt100.showAltState(false);
1036 vt100.lastSelectedKey.className = '';
1037 vt100.lastSelectedKey = undefined;
1039 elem.className = '';
1040 return false; }; }(this, elem, ch, key, CH, KEY);
1041 this.addListener(elem, 'mouseup', clicked);
1042 this.addListener(elem, 'click', clicked);
1044 // When moving the mouse away from a key, check if any keys need to be
1046 this.addListener(elem, 'mouseout',
1047 function(vt100, elem, key) { return function(e) {
1048 if (key == 16 /* Shift */) {
1049 if (!elem.className == vt100.isShift) {
1050 vt100.showShiftState(vt100.isShift);
1052 } else if (key == 17 /* Ctrl */) {
1053 if (!elem.className == vt100.isCtrl) {
1054 vt100.showCtrlState(vt100.isCtrl);
1056 } else if (key == 18 /* Alt */) {
1057 if (!elem.className == vt100.isAlt) {
1058 vt100.showAltState(vt100.isAlt);
1060 } else if (elem.className) {
1061 elem.className = '';
1062 vt100.lastSelectedKey = elem;
1063 } else if (vt100.lastSelectedKey) {
1064 vt100.resetLastSelectedKey(e);
1066 return false; }; }(this, elem, key));
1068 // When moving the mouse over a key, select it if the user is still holding
1069 // the mouse button down (i.e. elem == lastSelectedKey)
1070 this.addListener(elem, 'mouseover',
1071 function(vt100, elem, key) { return function(e) {
1072 if (elem == vt100.lastSelectedKey) {
1073 if (key == 16 /* Shift */) {
1074 if (!elem.className != vt100.isShift) {
1075 vt100.showShiftState(!vt100.isShift);
1077 } else if (key == 17 /* Ctrl */) {
1078 if (!elem.className != vt100.isCtrl) {
1079 vt100.showCtrlState(!vt100.isCtrl);
1081 } else if (key == 18 /* Alt */) {
1082 if (!elem.className != vt100.isAlt) {
1083 vt100.showAltState(!vt100.isAlt);
1085 } else if (!elem.className) {
1086 elem.className = 'selected';
1089 vt100.resetLastSelectedKey(e);
1091 return false; }; }(this, elem, key));
1094 VT100.prototype.initializeKeyBindings = function(elem) {
1096 if (elem.nodeName == "I" || elem.nodeName == "B") {
1098 // Function keys. The Javascript keycode is part of the "id"
1099 var i = parseInt(elem.id);
1101 // If the id does not parse as a number, it is not a keycode.
1102 this.addKeyBinding(elem, undefined, i);
1105 var child = elem.firstChild;
1107 if (child.nodeName == "#text") {
1108 // If the key only has a text node as a child, then it is a letter.
1109 // Automatically compute the lower and upper case version of the
1111 var text = this.getTextContent(child) ||
1112 this.getTextContent(elem);
1113 this.addKeyBinding(elem, text.toLowerCase());
1114 } else if (child.nextSibling) {
1115 // If the key has two children, they are the lower and upper case
1116 // character code, respectively.
1117 this.addKeyBinding(elem, this.getTextContent(child), undefined,
1118 this.getTextContent(child.nextSibling));
1124 // Recursively parse all other child nodes.
1125 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
1126 this.initializeKeyBindings(elem);
1130 VT100.prototype.initializeKeyboardButton = function() {
1131 // Configure mouse event handlers for button that displays/hides keyboard
1132 this.addListener(this.keyboardImage, 'click',
1133 function(vt100) { return function(e) {
1134 if (vt100.keyboard.style.display != '') {
1135 if (vt100.reconnectBtn.style.visibility != '') {
1136 vt100.initializeKeyboard();
1137 vt100.showSoftKeyboard();
1140 vt100.hideSoftKeyboard();
1141 vt100.input.focus();
1143 return false; }; }(this));
1145 // Enable button that displays keyboard
1146 if (this.softKeyboard) {
1147 this.keyboardImage.style.visibility = 'visible';
1151 VT100.prototype.initializeKeyboard = function() {
1152 // Only need to initialize the keyboard the very first time. When doing so,
1153 // copy the keyboard layout from the iframe.
1154 if (this.keyboard.firstChild) {
1157 this.keyboard.innerHTML =
1158 this.layout.contentDocument.body.innerHTML;
1159 var box = this.keyboard.firstChild;
1160 this.hideSoftKeyboard();
1162 // Configure mouse event handlers for on-screen keyboard
1163 this.addListener(this.keyboard, 'click',
1164 function(vt100) { return function(e) {
1165 vt100.hideSoftKeyboard();
1166 vt100.input.focus();
1167 return false; }; }(this));
1168 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
1169 this.addListener(box, 'click', this.cancelEvent);
1170 this.addListener(box, 'mouseup',
1171 function(vt100) { return function(e) {
1172 if (vt100.lastSelectedKey) {
1173 vt100.lastSelectedKey.className = '';
1174 vt100.lastSelectedKey = undefined;
1176 return false; }; }(this));
1177 this.addListener(box, 'mouseout',
1178 function(vt100) { return function(e) {
1179 return vt100.resetLastSelectedKey(e); }; }(this));
1180 this.addListener(box, 'mouseover',
1181 function(vt100) { return function(e) {
1182 return vt100.resetLastSelectedKey(e); }; }(this));
1184 // Configure SHIFT key behavior
1185 var style = document.createElement('style');
1186 var id = document.createAttribute('id');
1187 id.nodeValue = 'shift_state';
1188 style.setAttributeNode(id);
1189 var type = document.createAttribute('type');
1190 type.nodeValue = 'text/css';
1191 style.setAttributeNode(type);
1192 document.getElementsByTagName('head')[0].appendChild(style);
1194 // Set up key bindings
1195 this.initializeKeyBindings(box);
1198 VT100.prototype.initializeElements = function(container) {
1199 // If the necessary objects have not already been defined in the HTML
1200 // page, create them now.
1202 this.container = container;
1203 } else if (!(this.container = document.getElementById('vt100'))) {
1204 this.container = document.createElement('div');
1205 this.container.id = 'vt100';
1206 document.body.appendChild(this.container);
1209 if (!this.getChildById(this.container, 'reconnect') ||
1210 !this.getChildById(this.container, 'menu') ||
1211 !this.getChildById(this.container, 'keyboard') ||
1212 !this.getChildById(this.container, 'kbd_button') ||
1213 !this.getChildById(this.container, 'kbd_img') ||
1214 !this.getChildById(this.container, 'layout') ||
1215 !this.getChildById(this.container, 'scrollable') ||
1216 !this.getChildById(this.container, 'console') ||
1217 !this.getChildById(this.container, 'alt_console') ||
1218 !this.getChildById(this.container, 'ieprobe') ||
1219 !this.getChildById(this.container, 'padding') ||
1220 !this.getChildById(this.container, 'cursor') ||
1221 !this.getChildById(this.container, 'lineheight') ||
1222 !this.getChildById(this.container, 'usercss') ||
1223 !this.getChildById(this.container, 'space') ||
1224 !this.getChildById(this.container, 'input') ||
1225 !this.getChildById(this.container, 'cliphelper')) {
1226 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
1227 // we might get a pointless warning that a suitable plugin is not yet
1228 // installed. If in doubt, we'd rather just stay silent.
1231 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
1233 embed = typeof suppressAllAudio != 'undefined' &&
1234 suppressAllAudio ? "" :
1235 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
1236 'id="beep_embed" ' +
1238 'autostart="false" ' +
1240 'enablejavascript="true" ' +
1241 'type="audio/x-wav" ' +
1244 'style="position:absolute;left:-1000px;top:-1000px" />';
1249 this.container.innerHTML =
1250 '<div id="reconnect" style="visibility: hidden">' +
1251 '<input type="button" value="Connect" ' +
1252 'onsubmit="return false" />' +
1254 '<div id="cursize" style="visibility: hidden">' +
1256 '<div id="menu"></div>' +
1257 '<div id="keyboard" unselectable="on">' +
1259 '<div id="scrollable">' +
1260 '<table id="kbd_button">' +
1261 '<tr><td width="100%"> </td>' +
1262 '<td><img id="kbd_img" src="/webshell/keyboard.png" /></td>' +
1263 '<td> </td></tr>' +
1265 '<pre id="lineheight"> </pre>' +
1266 '<pre id="console">' +
1268 '<div id="ieprobe"><span> </span></div>' +
1270 '<pre id="alt_console" style="display: none"></pre>' +
1271 '<div id="padding"></div>' +
1272 '<pre id="cursor"> </pre>' +
1274 '<div class="hidden">' +
1275 '<div id="usercss"></div>' +
1276 '<pre><div><span id="space"></span></div></pre>' +
1277 '<input type="textfield" id="input" autocorrect="off" autocapitalize="off" />' +
1278 '<input type="textfield" id="cliphelper" />' +
1279 (typeof suppressAllAudio != 'undefined' &&
1280 suppressAllAudio ? "" :
1281 embed + '<bgsound id="beep_bgsound" loop=1 />') +
1282 '<iframe id="layout" src="/webshell/keyboard.html" />' +
1286 // Find the object used for playing the "beep" sound, if any.
1287 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
1288 this.beeper = undefined;
1290 this.beeper = this.getChildById(this.container,
1292 if (!this.beeper || !this.beeper.Play) {
1293 this.beeper = this.getChildById(this.container,
1295 if (!this.beeper || typeof this.beeper.src == 'undefined') {
1296 this.beeper = undefined;
1301 // Initialize the variables for finding the text console and the
1303 this.reconnectBtn = this.getChildById(this.container,'reconnect');
1304 this.curSizeBox = this.getChildById(this.container, 'cursize');
1305 this.menu = this.getChildById(this.container, 'menu');
1306 this.keyboard = this.getChildById(this.container, 'keyboard');
1307 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
1308 this.layout = this.getChildById(this.container, 'layout');
1309 this.scrollable = this.getChildById(this.container,
1311 this.lineheight = this.getChildById(this.container,
1314 [ this.getChildById(this.container, 'console'),
1315 this.getChildById(this.container, 'alt_console') ];
1316 var ieProbe = this.getChildById(this.container, 'ieprobe');
1317 this.padding = this.getChildById(this.container, 'padding');
1318 this.cursor = this.getChildById(this.container, 'cursor');
1319 this.usercss = this.getChildById(this.container, 'usercss');
1320 this.space = this.getChildById(this.container, 'space');
1321 this.input = this.getChildById(this.container, 'input');
1322 this.cliphelper = this.getChildById(this.container,
1325 // Add any user selectable style sheets to the menu
1326 this.initializeUserCSSStyles();
1328 // Remember the dimensions of a standard character glyph. We would
1329 // expect that we could just check cursor.clientWidth/Height at any time,
1330 // but it turns out that browsers sometimes invalidate these values
1331 // (e.g. while displaying a print preview screen).
1332 this.cursorWidth = this.cursor.clientWidth;
1333 this.cursorHeight = this.lineheight.clientHeight;
1335 // IE has a slightly different boxing model, that we need to compensate for
1336 this.isIE = ieProbe.offsetTop > 1;
1337 ieProbe = undefined;
1338 this.console.innerHTML = '';
1340 // Determine if the terminal window is positioned at the beginning of the
1341 // page, or if it is embedded somewhere else in the page. For full-screen
1342 // terminals, automatically resize whenever the browser window changes.
1343 var marginTop = parseInt(this.getCurrentComputedStyle(
1344 document.body, 'marginTop'));
1345 var marginLeft = parseInt(this.getCurrentComputedStyle(
1346 document.body, 'marginLeft'));
1347 var marginRight = parseInt(this.getCurrentComputedStyle(
1348 document.body, 'marginRight'));
1349 var x = this.container.offsetLeft;
1350 var y = this.container.offsetTop;
1351 for (var parent = this.container; parent = parent.offsetParent; ) {
1352 x += parent.offsetLeft;
1353 y += parent.offsetTop;
1355 this.isEmbedded = marginTop != y ||
1357 (window.innerWidth ||
1358 document.documentElement.clientWidth ||
1359 document.body.clientWidth) -
1360 marginRight != x + this.container.offsetWidth;
1361 if (!this.isEmbedded) {
1362 // Some browsers generate resize events when the terminal is first
1363 // shown. Disable showing the size indicator until a little bit after
1364 // the terminal has been rendered the first time.
1365 this.indicateSize = false;
1366 setTimeout(function(vt100) {
1368 vt100.indicateSize = true;
1371 this.addListener(window, 'resize',
1374 vt100.hideContextMenu();
1376 vt100.showCurrentSize();
1380 // Hide extra scrollbars attached to window
1381 document.body.style.margin = '0px';
1382 try { document.body.style.overflow ='hidden'; } catch (e) { }
1383 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1386 // Set up onscreen soft keyboard
1387 this.initializeKeyboardButton();
1389 // Hide context menu
1390 this.hideContextMenu();
1392 // Add listener to reconnect button
1393 this.addListener(this.reconnectBtn.firstChild, 'click',
1396 var rc = vt100.reconnect();
1397 vt100.input.focus();
1402 // Add input listeners
1403 this.addListener(this.input, 'blur',
1405 return function() { vt100.blurCursor(); } }(this));
1406 this.addListener(this.input, 'focus',
1408 return function() { vt100.focusCursor(); } }(this));
1409 this.addListener(this.input, 'keydown',
1411 return function(e) {
1412 if (!e) e = window.event;
1413 return vt100.keyDown(e); } }(this));
1414 this.addListener(this.input, 'keypress',
1416 return function(e) {
1417 if (!e) e = window.event;
1418 return vt100.keyPressed(e); } }(this));
1419 this.addListener(this.input, 'keyup',
1421 return function(e) {
1422 if (!e) e = window.event;
1423 return vt100.keyUp(e); } }(this));
1425 // Attach listeners that move the focus to the <input> field. This way we
1426 // can make sure that we can receive keyboard input.
1427 var mouseEvent = function(vt100, type) {
1428 return function(e) {
1429 if (!e) e = window.event;
1430 return vt100.mouseEvent(e, type);
1433 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1434 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
1435 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
1437 // Check that browser supports drag and drop
1438 if ('draggable' in document.createElement('span')) {
1439 var dropEvent = function (vt100) {
1440 return function(e) {
1441 if (!e) e = window.event;
1442 if (e.preventDefault) e.preventDefault();
1443 vt100.keysPressed(e.dataTransfer.getData('Text'));
1447 // Tell the browser that we *can* drop on this target
1448 this.addListener(this.scrollable, 'dragover', cancel);
1449 this.addListener(this.scrollable, 'dragenter', cancel);
1451 // Add a listener for the drop event
1452 this.addListener(this.scrollable, 'drop', dropEvent(this));
1455 // Initialize the blank terminal window.
1456 this.currentScreen = 0;
1459 this.numScrollbackLines = 0;
1461 this.bottom = 0x7FFFFFFF;
1468 function cancel(event) {
1469 if (event.preventDefault) {
1470 event.preventDefault();
1475 VT100.prototype.getChildById = function(parent, id) {
1476 var nodeList = parent.all || parent.getElementsByTagName('*');
1477 if (typeof nodeList.namedItem == 'undefined') {
1478 for (var i = 0; i < nodeList.length; i++) {
1479 if (nodeList[i].id == id) {
1485 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1486 return elem ? elem[0] || elem : null;
1490 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1491 if (typeof elem.currentStyle != 'undefined') {
1492 return elem.currentStyle[style];
1494 return document.defaultView.getComputedStyle(elem, null)[style];
1498 VT100.prototype.reconnect = function() {
1502 VT100.prototype.showReconnect = function(state) {
1504 this.hideSoftKeyboard();
1505 this.reconnectBtn.style.visibility = '';
1507 this.reconnectBtn.style.visibility = 'hidden';
1511 VT100.prototype.repairElements = function(console) {
1512 for (var line = console.firstChild; line; line = line.nextSibling) {
1513 if (!line.clientHeight) {
1514 var newLine = document.createElement(line.tagName);
1515 newLine.style.cssText = line.style.cssText;
1516 newLine.className = line.className;
1517 if (line.tagName == 'DIV') {
1518 for (var span = line.firstChild; span; span = span.nextSibling) {
1519 var newSpan = document.createElement(span.tagName);
1520 newSpan.style.cssText = span.style.cssText;
1521 newSpan.className = span.className;
1522 this.setTextContent(newSpan, this.getTextContent(span));
1523 newLine.appendChild(newSpan);
1526 this.setTextContent(newLine, this.getTextContent(line));
1528 line.parentNode.replaceChild(newLine, line);
1534 VT100.prototype.resized = function(w, h) {
1537 VT100.prototype.resizer = function() {
1538 // Hide onscreen soft keyboard
1539 this.hideSoftKeyboard();
1541 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1542 // Recreating it, will repair it.
1543 var newCursor = document.createElement('pre');
1544 this.setTextContent(newCursor, ' ');
1545 newCursor.id = 'cursor';
1546 newCursor.style.cssText = this.cursor.style.cssText;
1547 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1548 if (!newCursor.clientHeight) {
1549 // Things are broken right now. This is probably because we are
1550 // displaying the print-preview. Just don't change any of our settings
1551 // until the print dialog is closed again.
1552 newCursor.parentNode.removeChild(newCursor);
1555 // Swap the old broken cursor for the newly created one.
1556 this.cursor.parentNode.removeChild(this.cursor);
1557 this.cursor = newCursor;
1560 // Really horrible things happen if the contents of the terminal changes
1561 // while the print-preview is showing. We get HTML elements that show up
1562 // in the DOM, but that do not take up any space. Find these elements and
1564 this.repairElements(this.console[0]);
1565 this.repairElements(this.console[1]);
1567 // Lock the cursor size to the size of a normal character. This helps with
1568 // characters that are taller/shorter than normal. Unfortunately, we will
1569 // still get confused if somebody enters a character that is wider/narrower
1570 // than normal. This can happen if the browser tries to substitute a
1571 // characters from a different font.
1572 this.cursor.style.width = this.cursorWidth + 'px';
1573 this.cursor.style.height = this.cursorHeight + 'px';
1575 // Adjust height for one pixel padding of the #vt100 element.
1576 // The latter is necessary to properly display the inactive cursor.
1577 var console = this.console[this.currentScreen];
1578 var height = (this.isEmbedded ? this.container.clientHeight
1579 : (window.innerHeight ||
1580 document.documentElement.clientHeight ||
1581 document.body.clientHeight))-1;
1582 var partial = height % this.cursorHeight;
1583 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1584 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1585 var oldTerminalHeight = this.terminalHeight;
1587 this.updateHeight();
1589 // Clip the cursor to the visible screen.
1590 var cx = this.cursorX;
1591 var cy = this.cursorY + this.numScrollbackLines;
1593 // The alternate screen never keeps a scroll back buffer.
1594 this.updateNumScrollbackLines();
1595 while (this.currentScreen && this.numScrollbackLines > 0) {
1596 console.removeChild(console.firstChild);
1597 this.numScrollbackLines--;
1599 cy -= this.numScrollbackLines;
1602 } else if (cx > this.terminalWidth) {
1603 cx = this.terminalWidth - 1;
1610 } else if (cy > this.terminalHeight) {
1611 cy = this.terminalHeight - 1;
1617 // Clip the scroll region to the visible screen.
1618 if (this.bottom > this.terminalHeight ||
1619 this.bottom == oldTerminalHeight) {
1620 this.bottom = this.terminalHeight;
1622 if (this.top >= this.bottom) {
1623 this.top = this.bottom-1;
1629 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1630 // particularly important after changing the screen number), and reset
1631 // the scroll region to the default.
1632 this.truncateLines(this.terminalWidth);
1633 this.putString(cx, cy, '', undefined);
1634 this.scrollable.scrollTop = this.numScrollbackLines *
1635 this.cursorHeight + 1;
1637 // Update classNames for lines in the scrollback buffer
1638 var line = console.firstChild;
1639 for (var i = 0; i < this.numScrollbackLines; i++) {
1640 line.className = 'scrollback';
1641 line = line.nextSibling;
1644 line.className = '';
1645 line = line.nextSibling;
1648 // Reposition the reconnect button
1649 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1651 this.reconnectBtn.clientWidth)/2 + 'px';
1652 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1653 this.reconnectBtn.clientHeight)/2 + 'px';
1655 // Send notification that the window size has been changed
1656 this.resized(this.terminalWidth, this.terminalHeight);
1659 VT100.prototype.showCurrentSize = function() {
1660 if (!this.indicateSize) {
1663 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1664 this.terminalHeight;
1665 this.curSizeBox.style.left =
1666 (this.terminalWidth*this.cursorWidth/
1668 this.curSizeBox.clientWidth)/2 + 'px';
1669 this.curSizeBox.style.top =
1670 (this.terminalHeight*this.cursorHeight -
1671 this.curSizeBox.clientHeight)/2 + 'px';
1672 this.curSizeBox.style.visibility = '';
1673 if (this.curSizeTimeout) {
1674 clearTimeout(this.curSizeTimeout);
1677 // Only show the terminal size for a short amount of time after resizing.
1678 // Then hide this information, again. Some browsers generate resize events
1679 // throughout the entire resize operation. This is nice, and we will show
1680 // the terminal size while the user is dragging the window borders.
1681 // Other browsers only generate a single event when the user releases the
1682 // mouse. In those cases, we can only show the terminal size once at the
1683 // end of the resize operation.
1684 this.curSizeTimeout = setTimeout(function(vt100) {
1686 vt100.curSizeTimeout = null;
1687 vt100.curSizeBox.style.visibility = 'hidden';
1692 VT100.prototype.selection = function() {
1694 return '' + (window.getSelection && window.getSelection() ||
1695 document.selection && document.selection.type == 'Text' &&
1696 document.selection.createRange().text || '');
1702 VT100.prototype.cancelEvent = function(event) {
1704 // For non-IE browsers
1705 event.stopPropagation();
1706 event.preventDefault();
1711 event.cancelBubble = true;
1712 event.returnValue = false;
1720 VT100.prototype.mousePosition = function(event) {
1721 var offsetX = this.container.offsetLeft;
1722 var offsetY = this.container.offsetTop;
1723 for (var e = this.container; e = e.offsetParent; ) {
1724 offsetX += e.offsetLeft;
1725 offsetY += e.offsetTop;
1727 return [ event.clientX - offsetX,
1728 event.clientY - offsetY ];
1731 VT100.prototype.mouseEvent = function(event, type) {
1732 // If any text is currently selected, do not move the focus as that would
1733 // invalidate the selection.
1734 var selection = this.selection();
1735 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1739 // Compute mouse position in characters.
1740 var position = this.mousePosition(event);
1741 var x = Math.floor(position[0] / this.cursorWidth);
1742 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1743 this.cursorHeight) - this.numScrollbackLines;
1745 if (x >= this.terminalWidth) {
1746 x = this.terminalWidth - 1;
1753 if (y >= this.terminalHeight) {
1754 y = this.terminalHeight - 1;
1762 // Compute button number and modifier keys.
1763 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
1764 typeof event.pageX != 'undefined' ? event.button :
1765 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1766 if (button != undefined) {
1767 if (event.shiftKey) {
1770 if (event.altKey || event.metaKey) {
1773 if (event.ctrlKey) {
1778 // Report mouse events if they happen inside of the current screen and
1779 // with the SHIFT key unpressed. Both of these restrictions do not apply
1780 // for button releases, as we always want to report those.
1781 if (this.mouseReporting && !selection.length &&
1782 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1783 if (inside || type != 0 /* MOUSE_DOWN */) {
1784 if (button != undefined) {
1785 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1786 String.fromCharCode(x + 33) +
1787 String.fromCharCode(y + 33);
1788 if (type != 2 /* MOUSE_CLICK */) {
1789 this.keysPressed(report);
1792 // If we reported the event, stop propagating it (not sure, if this
1793 // actually works on most browsers; blocking the global "oncontextmenu"
1794 // even is still necessary).
1795 return this.cancelEvent(event);
1800 // Bring up context menu.
1801 if (button == 2 && !event.shiftKey) {
1802 if (type == 0 /* MOUSE_DOWN */) {
1803 this.showContextMenu(position[0], position[1]);
1805 return this.cancelEvent(event);
1808 if (this.mouseReporting) {
1810 event.shiftKey = false;
1818 VT100.prototype.replaceChar = function(s, ch, repl) {
1819 for (var i = -1;;) {
1820 i = s.indexOf(ch, i + 1);
1824 s = s.substr(0, i) + repl + s.substr(i + 1);
1829 VT100.prototype.htmlEscape = function(s) {
1830 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1831 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1834 VT100.prototype.getTextContent = function(elem) {
1835 return elem.textContent ||
1836 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1839 VT100.prototype.setTextContentRaw = function(elem, s) {
1840 // Updating the content of an element is an expensive operation. It actually
1841 // pays off to first check whether the element is still unchanged.
1842 if (typeof elem.textContent == 'undefined') {
1843 if (elem.innerText != s) {
1847 // Very old versions of IE do not allow setting innerText. Instead,
1848 // remove all children, by setting innerHTML and then set the text
1849 // using DOM methods.
1850 elem.innerHTML = '';
1851 elem.appendChild(document.createTextNode(
1852 this.replaceChar(s, ' ', '\u00A0')));
1856 if (elem.textContent != s) {
1857 elem.textContent = s;
1862 VT100.prototype.setTextContent = function(elem, s) {
1863 // Check if we find any URLs in the text. If so, automatically convert them
1865 if (this.urlRE && this.urlRE.test(s)) {
1869 if (RegExp.leftContext != null) {
1870 inner += this.htmlEscape(RegExp.leftContext);
1871 consumed += RegExp.leftContext.length;
1873 var url = this.htmlEscape(RegExp.lastMatch);
1876 // If no protocol was specified, try to guess a reasonable one.
1877 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1878 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1879 var slash = url.indexOf('/');
1880 var at = url.indexOf('@');
1881 var question = url.indexOf('?');
1883 (at < question || question < 0) &&
1884 (slash < 0 || (question > 0 && slash > question))) {
1885 fullUrl = 'mailto:' + url;
1887 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1892 inner += '<a target="vt100Link" href="' + fullUrl +
1893 '">' + url + '</a>';
1894 consumed += RegExp.lastMatch.length;
1895 s = s.substr(consumed);
1896 if (!this.urlRE.test(s)) {
1897 if (RegExp.rightContext != null) {
1898 inner += this.htmlEscape(RegExp.rightContext);
1903 elem.innerHTML = inner;
1907 this.setTextContentRaw(elem, s);
1910 VT100.prototype.insertBlankLine = function(y, color, style) {
1911 // Insert a blank line a position y. This method ignores the scrollback
1912 // buffer. The caller has to add the length of the scrollback buffer to
1913 // the position, if necessary.
1914 // If the position is larger than the number of current lines, this
1915 // method just adds a new line right after the last existing one. It does
1916 // not add any missing lines in between. It is the caller's responsibility
1919 color = 'ansi0 bgAnsi15';
1925 if (color != 'ansi0 bgAnsi15' && !style) {
1926 line = document.createElement('pre');
1927 this.setTextContent(line, '\n');
1929 line = document.createElement('div');
1930 var span = document.createElement('span');
1931 span.style.cssText = style;
1932 span.className = color;
1933 this.setTextContent(span, this.spaces(this.terminalWidth));
1934 line.appendChild(span);
1936 line.style.height = this.cursorHeight + 'px';
1937 var console = this.console[this.currentScreen];
1938 if (console.childNodes.length > y) {
1939 console.insertBefore(line, console.childNodes[y]);
1941 console.appendChild(line);
1945 VT100.prototype.updateWidth = function() {
1946 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1947 this.cursorWidth*this.scale);
1948 return this.terminalWidth;
1951 VT100.prototype.updateHeight = function() {
1952 // We want to be able to display either a terminal window that fills the
1953 // entire browser window, or a terminal window that is contained in a
1954 // <div> which is embededded somewhere in the web page.
1955 if (this.isEmbedded) {
1956 // Embedded terminal. Use size of the containing <div> (id="vt100").
1957 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1960 // Use the full browser window.
1961 this.terminalHeight = Math.floor(((window.innerHeight ||
1962 document.documentElement.clientHeight ||
1963 document.body.clientHeight)-1)/
1966 return this.terminalHeight;
1969 VT100.prototype.updateNumScrollbackLines = function() {
1970 var scrollback = Math.floor(
1971 this.console[this.currentScreen].offsetHeight /
1972 this.cursorHeight) -
1973 this.terminalHeight;
1974 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1975 return this.numScrollbackLines;
1978 VT100.prototype.truncateLines = function(width) {
1982 for (var line = this.console[this.currentScreen].firstChild; line;
1983 line = line.nextSibling) {
1984 if (line.tagName == 'DIV') {
1987 // Traverse current line and truncate it once we saw "width" characters
1988 for (var span = line.firstChild; span;
1989 span = span.nextSibling) {
1990 var s = this.getTextContent(span);
1992 if (x + l > width) {
1993 this.setTextContent(span, s.substr(0, width - x));
1994 while (span.nextSibling) {
1995 line.removeChild(line.lastChild);
2001 // Prune white space from the end of the current line
2002 var span = line.lastChild;
2004 span.className == 'ansi0 bgAnsi15' &&
2005 !span.style.cssText.length) {
2006 // Scan backwards looking for first non-space character
2007 var s = this.getTextContent(span);
2008 for (var i = s.length; i--; ) {
2009 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2010 if (i+1 != s.length) {
2011 this.setTextContent(s.substr(0, i+1));
2019 span = span.previousSibling;
2021 // Remove blank <span>'s from end of line
2022 line.removeChild(sibling);
2024 // Remove entire line (i.e. <div>), if empty
2025 var blank = document.createElement('pre');
2026 blank.style.height = this.cursorHeight + 'px';
2027 this.setTextContent(blank, '\n');
2028 line.parentNode.replaceChild(blank, line);
2036 VT100.prototype.putString = function(x, y, text, color, style) {
2038 color = 'ansi0 bgAnsi15';
2043 var yIdx = y + this.numScrollbackLines;
2049 var console = this.console[this.currentScreen];
2050 if (!text.length && (yIdx >= console.childNodes.length ||
2051 console.childNodes[yIdx].tagName != 'DIV')) {
2052 // Positioning cursor to a blank location
2055 // Create missing blank lines at end of page
2056 while (console.childNodes.length <= yIdx) {
2057 // In order to simplify lookups, we want to make sure that each line
2058 // is represented by exactly one element (and possibly a whole bunch of
2060 // For non-blank lines, we can create a <div> containing one or more
2061 // <span>s. For blank lines, this fails as browsers tend to optimize them
2062 // away. But fortunately, a <pre> tag containing a newline character
2063 // appears to work for all browsers (a would also work, but then
2064 // copying from the browser window would insert superfluous spaces into
2066 this.insertBlankLine(yIdx);
2068 line = console.childNodes[yIdx];
2070 // If necessary, promote blank '\n' line to a <div> tag
2071 if (line.tagName != 'DIV') {
2072 var div = document.createElement('div');
2073 div.style.height = this.cursorHeight + 'px';
2074 div.innerHTML = '<span></span>';
2075 console.replaceChild(div, line);
2079 // Scan through list of <span>'s until we find the one where our text
2081 span = line.firstChild;
2083 while (span.nextSibling && xPos < x) {
2084 len = this.getTextContent(span).length;
2085 if (xPos + len > x) {
2089 span = span.nextSibling;
2093 // If current <span> is not long enough, pad with spaces or add new
2095 s = this.getTextContent(span);
2096 var oldColor = span.className;
2097 var oldStyle = span.style.cssText;
2098 if (xPos + s.length < x) {
2099 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
2100 span = document.createElement('span');
2101 line.appendChild(span);
2102 span.className = 'ansi0 bgAnsi15';
2103 span.style.cssText = '';
2104 oldColor = 'ansi0 bgAnsi15';
2111 } while (xPos + s.length < x);
2114 // If styles do not match, create a new <span>
2115 var del = text.length - s.length + x - xPos;
2116 if (oldColor != color ||
2117 (oldStyle != style && (oldStyle || style))) {
2119 // Replacing text at beginning of existing <span>
2120 if (text.length >= s.length) {
2121 // New text is equal or longer than existing text
2124 // Insert new <span> before the current one, then remove leading
2125 // part of existing <span>, adjust style of new <span>, and finally
2127 sibling = document.createElement('span');
2128 line.insertBefore(sibling, span);
2129 this.setTextContent(span, s.substr(text.length));
2134 // Replacing text some way into the existing <span>
2135 var remainder = s.substr(x + text.length - xPos);
2136 this.setTextContent(span, s.substr(0, x - xPos));
2138 sibling = document.createElement('span');
2139 if (span.nextSibling) {
2140 line.insertBefore(sibling, span.nextSibling);
2142 if (remainder.length) {
2143 sibling = document.createElement('span');
2144 sibling.className = oldColor;
2145 sibling.style.cssText = oldStyle;
2146 this.setTextContent(sibling, remainder);
2147 line.insertBefore(sibling, span.nextSibling);
2150 line.appendChild(sibling);
2152 if (remainder.length) {
2153 sibling = document.createElement('span');
2154 sibling.className = oldColor;
2155 sibling.style.cssText = oldStyle;
2156 this.setTextContent(sibling, remainder);
2157 line.appendChild(sibling);
2162 span.className = color;
2163 span.style.cssText = style;
2165 // Overwrite (partial) <span> with new text
2166 s = s.substr(0, x - xPos) +
2168 s.substr(x + text.length - xPos);
2170 this.setTextContent(span, s);
2173 // Delete all subsequent <span>'s that have just been overwritten
2174 sibling = span.nextSibling;
2175 while (del > 0 && sibling) {
2176 s = this.getTextContent(sibling);
2179 line.removeChild(sibling);
2181 sibling = span.nextSibling;
2183 this.setTextContent(sibling, s.substr(del));
2188 // Merge <span> with next sibling, if styles are identical
2189 if (sibling && span.className == sibling.className &&
2190 span.style.cssText == sibling.style.cssText) {
2191 this.setTextContent(span,
2192 this.getTextContent(span) +
2193 this.getTextContent(sibling));
2194 line.removeChild(sibling);
2200 this.cursorX = x + text.length;
2201 if (this.cursorX >= this.terminalWidth) {
2202 this.cursorX = this.terminalWidth - 1;
2203 if (this.cursorX < 0) {
2209 if (!this.cursor.style.visibility) {
2210 var idx = this.cursorX - xPos;
2212 // If we are in a non-empty line, take the cursor Y position from the
2213 // other elements in this line. If dealing with broken, non-proportional
2214 // fonts, this is likely to yield better results.
2215 pixelY = span.offsetTop +
2216 span.offsetParent.offsetTop;
2217 s = this.getTextContent(span);
2218 var nxtIdx = idx - s.length;
2220 this.setTextContent(this.cursor, s.charAt(idx));
2221 pixelX = span.offsetLeft +
2222 idx*span.offsetWidth / s.length;
2225 pixelX = span.offsetLeft + span.offsetWidth;
2227 if (span.nextSibling) {
2228 s = this.getTextContent(span.nextSibling);
2229 this.setTextContent(this.cursor, s.charAt(nxtIdx));
2231 pixelX = span.nextSibling.offsetLeft +
2232 nxtIdx*span.offsetWidth / s.length;
2235 this.setTextContent(this.cursor, ' ');
2239 this.setTextContent(this.cursor, ' ');
2243 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
2246 this.setTextContent(this.space, this.spaces(this.cursorX));
2247 this.cursor.style.left = (this.space.offsetWidth +
2248 console.offsetLeft)/this.scale + 'px';
2250 this.cursorY = yIdx - this.numScrollbackLines;
2252 this.cursor.style.top = pixelY + 'px';
2254 this.cursor.style.top = yIdx*this.cursorHeight +
2255 console.offsetTop + 'px';
2259 // Merge <span> with previous sibling, if styles are identical
2260 if ((sibling = span.previousSibling) &&
2261 span.className == sibling.className &&
2262 span.style.cssText == sibling.style.cssText) {
2263 this.setTextContent(span,
2264 this.getTextContent(sibling) +
2265 this.getTextContent(span));
2266 line.removeChild(sibling);
2269 // Prune white space from the end of the current line
2270 span = line.lastChild;
2272 span.className == 'ansi0 bgAnsi15' &&
2273 !span.style.cssText.length) {
2274 // Scan backwards looking for first non-space character
2275 s = this.getTextContent(span);
2276 for (var i = s.length; i--; ) {
2277 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2278 if (i+1 != s.length) {
2279 this.setTextContent(s.substr(0, i+1));
2287 span = span.previousSibling;
2289 // Remove blank <span>'s from end of line
2290 line.removeChild(sibling);
2292 // Remove entire line (i.e. <div>), if empty
2293 var blank = document.createElement('pre');
2294 blank.style.height = this.cursorHeight + 'px';
2295 this.setTextContent(blank, '\n');
2296 line.parentNode.replaceChild(blank, line);
2303 VT100.prototype.gotoXY = function(x, y) {
2304 if (x >= this.terminalWidth) {
2305 x = this.terminalWidth - 1;
2311 if (this.offsetMode) {
2316 maxY = this.terminalHeight;
2324 this.putString(x, y, '', undefined);
2325 this.needWrap = false;
2328 VT100.prototype.gotoXaY = function(x, y) {
2329 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
2332 VT100.prototype.refreshInvertedState = function() {
2333 if (this.isInverted) {
2334 this.scrollable.className += ' inverted';
2336 this.scrollable.className = this.scrollable.className.
2337 replace(/ *inverted/, '');
2341 VT100.prototype.enableAlternateScreen = function(state) {
2342 // Don't do anything, if we are already on the desired screen
2343 if ((state ? 1 : 0) == this.currentScreen) {
2344 // Calling the resizer is not actually necessary. But it is a good way
2345 // of resetting state that might have gotten corrupted.
2350 // We save the full state of the normal screen, when we switch away from it.
2351 // But for the alternate screen, no saving is necessary. We always reset
2352 // it when we switch to it.
2357 // Display new screen, and initialize state (the resizer does that for us).
2358 this.currentScreen = state ? 1 : 0;
2359 this.console[1-this.currentScreen].style.display = 'none';
2360 this.console[this.currentScreen].style.display = '';
2362 // Select appropriate character pitch.
2363 var transform = this.getTransformName();
2366 // Upon enabling the alternate screen, we switch to 80 column mode. But
2367 // upon returning to the regular screen, we restore the mode that was
2368 // in effect previously.
2369 this.console[1].style[transform] = '';
2372 this.console[this.currentScreen].style[transform];
2373 this.cursor.style[transform] = style;
2374 this.space.style[transform] = style;
2375 this.scale = style == '' ? 1.0:1.65;
2376 if (transform == 'filter') {
2377 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
2382 // If we switched to the alternate screen, reset it completely. Otherwise,
2383 // restore the saved state.
2386 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2388 this.restoreCursor();
2392 VT100.prototype.hideCursor = function() {
2393 var hidden = this.cursor.style.visibility == 'hidden';
2395 this.cursor.style.visibility = 'hidden';
2401 VT100.prototype.showCursor = function(x, y) {
2402 if (this.cursor.style.visibility) {
2403 this.cursor.style.visibility = '';
2404 this.putString(x == undefined ? this.cursorX : x,
2405 y == undefined ? this.cursorY : y,
2412 VT100.prototype.scrollBack = function() {
2413 var i = this.scrollable.scrollTop -
2414 this.scrollable.clientHeight;
2415 this.scrollable.scrollTop = i < 0 ? 0 : i;
2418 VT100.prototype.scrollFore = function() {
2419 var i = this.scrollable.scrollTop +
2420 this.scrollable.clientHeight;
2421 this.scrollable.scrollTop = i > this.numScrollbackLines *
2422 this.cursorHeight + 1
2423 ? this.numScrollbackLines *
2424 this.cursorHeight + 1
2428 VT100.prototype.spaces = function(i) {
2436 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2441 if (w > this.terminalWidth) {
2442 w = this.terminalWidth;
2444 if ((w -= x) <= 0) {
2451 if (h > this.terminalHeight) {
2452 h = this.terminalHeight;
2454 if ((h -= y) <= 0) {
2458 // Special case the situation where we clear the entire screen, and we do
2459 // not have a scrollback buffer. In that case, we should just remove all
2461 if (!this.numScrollbackLines &&
2462 w == this.terminalWidth && h == this.terminalHeight &&
2463 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2464 var console = this.console[this.currentScreen];
2465 while (console.lastChild) {
2466 console.removeChild(console.lastChild);
2468 this.putString(this.cursorX, this.cursorY, '', undefined);
2470 var hidden = this.hideCursor();
2471 var cx = this.cursorX;
2472 var cy = this.cursorY;
2473 var s = this.spaces(w);
2474 for (var i = y+h; i-- > y; ) {
2475 this.putString(x, i, s, color, style);
2477 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2481 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2483 var className = [ ];
2485 var console = this.console[this.currentScreen];
2486 if (sY >= console.childNodes.length) {
2487 text[0] = this.spaces(w);
2488 className[0] = undefined;
2489 style[0] = undefined;
2491 var line = console.childNodes[sY];
2492 if (line.tagName != 'DIV' || !line.childNodes.length) {
2493 text[0] = this.spaces(w);
2494 className[0] = undefined;
2495 style[0] = undefined;
2498 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2499 var s = this.getTextContent(span);
2502 var o = sX > x ? sX - x : 0;
2503 text[text.length] = s.substr(o, w);
2504 className[className.length] = span.className;
2505 style[style.length] = span.style.cssText;
2511 text[text.length] = this.spaces(w);
2512 className[className.length] = undefined;
2513 style[style.length] = undefined;
2517 var hidden = this.hideCursor();
2518 var cx = this.cursorX;
2519 var cy = this.cursorY;
2520 for (var i = 0; i < text.length; i++) {
2523 color = className[i];
2525 color = 'ansi0 bgAnsi15';
2527 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2528 dX += text[i].length;
2530 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2533 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2535 var left = incX < 0 ? -incX : 0;
2536 var right = incX > 0 ? incX : 0;
2537 var up = incY < 0 ? -incY : 0;
2538 var down = incY > 0 ? incY : 0;
2540 // Clip region against terminal size
2541 var dontScroll = null;
2546 if (w > this.terminalWidth - right) {
2547 w = this.terminalWidth - right;
2549 if ((w -= x) <= 0) {
2556 if (h > this.terminalHeight - down) {
2557 h = this.terminalHeight - down;
2563 if (style && style.indexOf('underline')) {
2564 // Different terminal emulators disagree on the attributes that
2565 // are used for scrolling. The consensus seems to be, never to
2566 // fill with underlined spaces. N.B. this is different from the
2567 // cases when the user blanks a region. User-initiated blanking
2568 // always fills with all of the current attributes.
2569 style = style.replace(/text-decoration:underline;/, '');
2572 // Compute current scroll position
2573 var scrollPos = this.numScrollbackLines -
2574 (this.scrollable.scrollTop-1) / this.cursorHeight;
2576 // Determine original cursor position. Hide cursor temporarily to avoid
2577 // visual artifacts.
2578 var hidden = this.hideCursor();
2579 var cx = this.cursorX;
2580 var cy = this.cursorY;
2581 var console = this.console[this.currentScreen];
2583 if (!incX && !x && w == this.terminalWidth) {
2584 // Scrolling entire lines
2587 if (!this.currentScreen && y == -incY &&
2588 h == this.terminalHeight + incY) {
2589 // Scrolling up with adding to the scrollback buffer. This is only
2590 // possible if there are at least as many lines in the console,
2591 // as the terminal is high
2592 while (console.childNodes.length < this.terminalHeight) {
2593 this.insertBlankLine(this.terminalHeight);
2596 // Add new lines at bottom in order to force scrolling
2597 for (var i = 0; i < y; i++) {
2598 this.insertBlankLine(console.childNodes.length, color, style);
2601 // Adjust the number of lines in the scrollback buffer by
2602 // removing excess entries.
2603 this.updateNumScrollbackLines();
2604 while (this.numScrollbackLines >
2605 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2606 console.removeChild(console.firstChild);
2607 this.numScrollbackLines--;
2610 // Mark lines in the scrollback buffer, so that they do not get
2612 for (var i = this.numScrollbackLines, j = -incY;
2613 i-- > 0 && j-- > 0; ) {
2614 console.childNodes[i].className = 'scrollback';
2617 // Scrolling up without adding to the scrollback buffer.
2620 console.childNodes.length >
2621 this.numScrollbackLines + y + incY; ) {
2622 console.removeChild(console.childNodes[
2623 this.numScrollbackLines + y + incY]);
2626 // If we used to have a scrollback buffer, then we must make sure
2627 // that we add back blank lines at the bottom of the terminal.
2628 // Similarly, if we are scrolling in the middle of the screen,
2629 // we must add blank lines to ensure that the bottom of the screen
2630 // does not move up.
2631 if (this.numScrollbackLines > 0 ||
2632 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2633 for (var i = -incY; i-- > 0; ) {
2634 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2643 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2644 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2646 for (var i = incY; i--; ) {
2647 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2651 // Scrolling partial lines
2653 // Scrolling up or horizontally within a line
2654 for (var i = y + this.numScrollbackLines;
2655 i < y + this.numScrollbackLines + h;
2657 this.copyLineSegment(x + incX, i + incY, x, i, w);
2661 for (var i = y + this.numScrollbackLines + h;
2662 i-- > y + this.numScrollbackLines; ) {
2663 this.copyLineSegment(x + incX, i + incY, x, i, w);
2667 // Clear blank regions
2669 this.clearRegion(x, y, incX, h, color, style);
2670 } else if (incX < 0) {
2671 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2674 this.clearRegion(x, y, w, incY, color, style);
2675 } else if (incY < 0) {
2676 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2680 // Reset scroll position
2681 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2682 this.cursorHeight + 1;
2684 // Move cursor back to its original position
2685 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2689 VT100.prototype.copy = function(selection) {
2690 if (selection == undefined) {
2691 selection = this.selection();
2693 this.internalClipboard = undefined;
2694 if (selection.length) {
2697 this.cliphelper.value = selection;
2698 this.cliphelper.select();
2699 this.cliphelper.createTextRange().execCommand('copy');
2701 this.internalClipboard = selection;
2703 this.cliphelper.value = '';
2707 VT100.prototype.copyLast = function() {
2708 // Opening the context menu can remove the selection. We try to prevent this
2709 // from happening, but that is not possible for all browsers. So, instead,
2710 // we compute the selection before showing the menu.
2711 this.copy(this.lastSelection);
2714 VT100.prototype.pasteFnc = function() {
2715 var clipboard = undefined;
2716 if (this.internalClipboard != undefined) {
2717 clipboard = this.internalClipboard;
2720 this.cliphelper.value = '';
2721 this.cliphelper.createTextRange().execCommand('paste');
2722 clipboard = this.cliphelper.value;
2726 this.cliphelper.value = '';
2727 if (clipboard && this.menu.style.visibility == 'hidden') {
2729 this.keysPressed('' + clipboard);
2736 VT100.prototype.pasteBrowserFnc = function() {
2737 var clipboard = prompt("Paste into this box:","");
2738 if (clipboard != undefined) {
2739 return this.keysPressed('' + clipboard);
2743 VT100.prototype.toggleUTF = function() {
2744 this.utfEnabled = !this.utfEnabled;
2746 // We always persist the last value that the user selected. Not necessarily
2747 // the last value that a random program requested.
2748 this.utfPreferred = this.utfEnabled;
2751 VT100.prototype.toggleBell = function() {
2752 this.visualBell = !this.visualBell;
2755 VT100.prototype.toggleSoftKeyboard = function() {
2756 this.softKeyboard = !this.softKeyboard;
2757 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2760 VT100.prototype.deselectKeys = function(elem) {
2761 if (elem && elem.className == 'selected') {
2762 elem.className = '';
2764 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2765 this.deselectKeys(elem);
2769 VT100.prototype.showSoftKeyboard = function() {
2770 // Make sure no key is currently selected
2771 this.lastSelectedKey = undefined;
2772 this.deselectKeys(this.keyboard);
2773 this.isShift = false;
2774 this.showShiftState(false);
2775 this.isCtrl = false;
2776 this.showCtrlState(false);
2778 this.showAltState(false);
2780 this.keyboard.style.left = '0px';
2781 this.keyboard.style.top = '0px';
2782 this.keyboard.style.width = this.container.offsetWidth + 'px';
2783 this.keyboard.style.height = this.container.offsetHeight + 'px';
2784 this.keyboard.style.visibility = 'hidden';
2785 this.keyboard.style.display = '';
2787 var kbd = this.keyboard.firstChild;
2789 var transform = this.getTransformName();
2791 kbd.style[transform] = '';
2792 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2793 scale = (kbd.offsetWidth/
2794 this.container.offsetWidth)/0.9;
2796 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2797 scale = Math.max((kbd.offsetHeight/
2798 this.container.offsetHeight)/0.9);
2800 var style = this.getTransformStyle(transform,
2801 scale > 1.0 ? scale : undefined);
2802 kbd.style[transform] = style;
2804 if (transform == 'filter') {
2807 kbd.style.left = ((this.container.offsetWidth -
2808 kbd.offsetWidth/scale)/2) + 'px';
2809 kbd.style.top = ((this.container.offsetHeight -
2810 kbd.offsetHeight/scale)/2) + 'px';
2812 this.keyboard.style.visibility = 'visible';
2815 VT100.prototype.hideSoftKeyboard = function() {
2816 this.keyboard.style.display = 'none';
2819 VT100.prototype.toggleCursorBlinking = function() {
2820 this.blinkingCursor = !this.blinkingCursor;
2823 VT100.prototype.about = function() {
2824 alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2825 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2826 "For more information check http://shellinabox.com");
2829 VT100.prototype.hideContextMenu = function() {
2830 this.menu.style.visibility = 'hidden';
2831 this.menu.style.top = '-100px';
2832 this.menu.style.left = '-100px';
2833 this.menu.style.width = '0px';
2834 this.menu.style.height = '0px';
2837 VT100.prototype.extendContextMenu = function(entries, actions) {
2840 VT100.prototype.showContextMenu = function(x, y) {
2841 this.menu.innerHTML =
2842 '<table class="popup" ' +
2843 'cellpadding="0" cellspacing="0">' +
2845 '<ul id="menuentries">' +
2846 '<li id="beginclipboard">Copy</li>' +
2847 '<li id="endclipboard">Paste</li>' +
2848 '<li id="browserclipboard">Paste from browser</li>' +
2850 '<li id="reset">Reset</li>' +
2852 '<li id="beginconfig">' +
2853 (this.utfEnabled ? '<img src="/webshell/enabled.gif" />' : '') +
2856 (this.visualBell ? '<img src="/webshell/enabled.gif" />' : '') +
2859 (this.softKeyboard ? '<img src="/webshell/enabled.gif" />' : '') +
2860 'Onscreen Keyboard</li>' +
2861 '<li id="endconfig">' +
2862 (this.blinkingCursor ? '<img src="/webshell/enabled.gif" />' : '') +
2863 'Blinking Cursor</li>'+
2864 (this.usercss.firstChild ?
2865 '<hr id="beginusercss" />' +
2866 this.usercss.innerHTML +
2867 '<hr id="endusercss" />' :
2869 '<li id="about">About...</li>' +
2874 var popup = this.menu.firstChild;
2875 var menuentries = this.getChildById(popup, 'menuentries');
2877 // Determine menu entries that should be disabled
2878 this.lastSelection = this.selection();
2879 if (!this.lastSelection.length) {
2880 menuentries.firstChild.className
2883 var p = this.pasteFnc();
2885 menuentries.childNodes[1].className
2889 // Actions for default items
2890 var actions = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2891 this.toggleUTF, this.toggleBell,
2892 this.toggleSoftKeyboard,
2893 this.toggleCursorBlinking ];
2895 // Actions for user CSS styles (if any)
2896 for (var i = 0; i < this.usercssActions.length; ++i) {
2897 actions[actions.length] = this.usercssActions[i];
2899 actions[actions.length] = this.about;
2901 // Allow subclasses to dynamically add entries to the context menu
2902 this.extendContextMenu(menuentries, actions);
2904 // Hook up event listeners
2905 for (var node = menuentries.firstChild, i = 0; node;
2906 node = node.nextSibling) {
2907 if (node.tagName == 'LI') {
2908 if (node.className != 'disabled') {
2909 this.addListener(node, 'mouseover',
2910 function(vt100, node) {
2912 node.className = 'hover';
2915 this.addListener(node, 'mouseout',
2916 function(vt100, node) {
2918 node.className = '';
2921 this.addListener(node, 'mousedown',
2922 function(vt100, action) {
2923 return function(event) {
2924 vt100.hideContextMenu();
2926 vt100.storeUserSettings();
2927 return vt100.cancelEvent(event || window.event);
2929 }(this, actions[i]));
2930 this.addListener(node, 'mouseup',
2932 return function(event) {
2933 return vt100.cancelEvent(event || window.event);
2936 this.addListener(node, 'mouseclick',
2938 return function(event) {
2939 return vt100.cancelEvent(event || window.event);
2947 // Position menu next to the mouse pointer
2948 this.menu.style.left = '0px';
2949 this.menu.style.top = '0px';
2950 this.menu.style.width = this.container.offsetWidth + 'px';
2951 this.menu.style.height = this.container.offsetHeight + 'px';
2952 popup.style.left = '0px';
2953 popup.style.top = '0px';
2956 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2957 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2962 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2963 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2968 popup.style.left = x + 'px';
2969 popup.style.top = y + 'px';
2971 // Block all other interactions with the terminal emulator
2972 this.addListener(this.menu, 'click', function(vt100) {
2974 vt100.hideContextMenu();
2979 this.menu.style.visibility = '';
2982 VT100.prototype.keysPressed = function(ch) {
2983 for (var i = 0; i < ch.length; i++) {
2984 var c = ch.charCodeAt(i);
2985 this.vt100(c >= 7 && c <= 15 ||
2986 c == 24 || c == 26 || c == 27 || c >= 32
2987 ? String.fromCharCode(c) : '<' + c + '>');
2991 VT100.prototype.applyModifiers = function(ch, event) {
2993 if (event.ctrlKey) {
2994 if (ch >= 32 && ch <= 127) {
2995 // For historic reasons, some control characters are treated specially
2997 case /* 3 */ 51: ch = 27; break;
2998 case /* 4 */ 52: ch = 28; break;
2999 case /* 5 */ 53: ch = 29; break;
3000 case /* 6 */ 54: ch = 30; break;
3001 case /* 7 */ 55: ch = 31; break;
3002 case /* 8 */ 56: ch = 127; break;
3003 case /* ? */ 63: ch = 127; break;
3004 default: ch &= 31; break;
3008 return String.fromCharCode(ch);
3014 VT100.prototype.handleKey = function(event) {
3015 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
3016 // (event.shiftKey || event.ctrlKey || event.altKey ||
3017 // event.metaKey ? ', ' +
3018 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3019 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3022 if (typeof event.charCode != 'undefined') {
3023 // non-IE keypress events have a translated charCode value. Also, our
3024 // fake events generated when receiving keydown events include this data
3026 ch = event.charCode;
3027 key = event.keyCode;
3029 // When sending a keypress event, IE includes the translated character
3030 // code in the keyCode field.
3035 // Apply modifier keys (ctrl and shift)
3039 ch = this.applyModifiers(ch, event);
3041 // By this point, "ch" is either defined and contains the character code, or
3042 // it is undefined and "key" defines the code of a function key
3043 if (ch != undefined) {
3044 this.scrollable.scrollTop = this.numScrollbackLines *
3045 this.cursorHeight + 1;
3047 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
3048 // Many programs have difficulties dealing with parametrized escape
3049 // sequences for function keys. Thus, if ALT is the only modifier
3050 // key, return Emacs-style keycodes for commonly used keys.
3052 case 33: /* Page Up */ ch = '\u001B<'; break;
3053 case 34: /* Page Down */ ch = '\u001B>'; break;
3054 case 37: /* Left */ ch = '\u001Bb'; break;
3055 case 38: /* Up */ ch = '\u001Bp'; break;
3056 case 39: /* Right */ ch = '\u001Bf'; break;
3057 case 40: /* Down */ ch = '\u001Bn'; break;
3058 case 46: /* Delete */ ch = '\u001Bd'; break;
3061 } else if (event.shiftKey && !event.ctrlKey &&
3062 !event.altKey && !event.metaKey) {
3064 case 33: /* Page Up */ this.scrollBack(); return;
3065 case 34: /* Page Down */ this.scrollFore(); return;
3069 if (ch == undefined) {
3071 case 8: /* Backspace */ ch = '\u007f'; break;
3072 case 9: /* Tab */ ch = '\u0009'; break;
3073 case 10: /* Return */ ch = '\u000A'; break;
3074 case 13: /* Enter */ ch = this.crLfMode ?
3075 '\r\n' : '\r'; break;
3076 case 16: /* Shift */ return;
3077 case 17: /* Ctrl */ return;
3078 case 18: /* Alt */ return;
3079 case 19: /* Break */ return;
3080 case 20: /* Caps Lock */ return;
3081 case 27: /* Escape */ ch = '\u001B'; break;
3082 case 33: /* Page Up */ ch = '\u001B[5~'; break;
3083 case 34: /* Page Down */ ch = '\u001B[6~'; break;
3084 case 35: /* End */ ch = '\u001BOF'; break;
3085 case 36: /* Home */ ch = '\u001BOH'; break;
3086 case 37: /* Left */ ch = this.cursorKeyMode ?
3087 '\u001BOD' : '\u001B[D'; break;
3088 case 38: /* Up */ ch = this.cursorKeyMode ?
3089 '\u001BOA' : '\u001B[A'; break;
3090 case 39: /* Right */ ch = this.cursorKeyMode ?
3091 '\u001BOC' : '\u001B[C'; break;
3092 case 40: /* Down */ ch = this.cursorKeyMode ?
3093 '\u001BOB' : '\u001B[B'; break;
3094 case 45: /* Insert */ ch = '\u001B[2~'; break;
3095 case 46: /* Delete */ ch = '\u001B[3~'; break;
3096 case 91: /* Left Window */ return;
3097 case 92: /* Right Window */ return;
3098 case 93: /* Select */ return;
3099 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
3100 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
3101 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
3102 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
3103 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
3104 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
3105 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
3106 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
3107 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
3108 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
3109 case 106: /* * */ ch = this.applyModifiers(42, event); break;
3110 case 107: /* + */ ch = this.applyModifiers(43, event); break;
3111 case 109: /* - */ ch = this.applyModifiers(45, event); break;
3112 case 110: /* . */ ch = this.applyModifiers(46, event); break;
3113 case 111: /* / */ ch = this.applyModifiers(47, event); break;
3114 case 112: /* F1 */ ch = '\u001BOP'; break;
3115 case 113: /* F2 */ ch = '\u001BOQ'; break;
3116 case 114: /* F3 */ ch = '\u001BOR'; break;
3117 case 115: /* F4 */ ch = '\u001BOS'; break;
3118 case 116: /* F5 */ ch = '\u001B[15~'; break;
3119 case 117: /* F6 */ ch = '\u001B[17~'; break;
3120 case 118: /* F7 */ ch = '\u001B[18~'; break;
3121 case 119: /* F8 */ ch = '\u001B[19~'; break;
3122 case 120: /* F9 */ ch = '\u001B[20~'; break;
3123 case 121: /* F10 */ ch = '\u001B[21~'; break;
3124 case 122: /* F11 */ ch = '\u001B[23~'; break;
3125 case 123: /* F12 */ ch = '\u001B[24~'; break;
3126 case 144: /* Num Lock */ return;
3127 case 145: /* Scroll Lock */ return;
3128 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
3129 case 187: /* = */ ch = this.applyModifiers(61, event); break;
3130 case 188: /* , */ ch = this.applyModifiers(44, event); break;
3131 case 189: /* - */ ch = this.applyModifiers(45, event); break;
3132 case 173: /* - */ ch = this.applyModifiers(45, event); break; // FF15 Patch
3133 case 190: /* . */ ch = this.applyModifiers(46, event); break;
3134 case 191: /* / */ ch = this.applyModifiers(47, event); break;
3135 // Conflicts with dead key " on Swiss keyboards
3136 //case 192: /* ` */ ch = this.applyModifiers(96, event); break;
3137 // Conflicts with dead key " on Swiss keyboards
3138 //case 219: /* [ */ ch = this.applyModifiers(91, event); break;
3139 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
3140 // Conflicts with dead key ^ and ` on Swiss keaboards
3141 // ^ and " on French keyboards
3142 //case 221: /* ] */ ch = this.applyModifiers(93, event); break;
3143 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
3146 this.scrollable.scrollTop = this.numScrollbackLines *
3147 this.cursorHeight + 1;
3151 // "ch" now contains the sequence of keycodes to send. But we might still
3152 // have to apply the effects of modifier keys.
3153 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
3154 var start, digit, part1, part2;
3155 if ((start = ch.substr(0, 2)) == '\u001B[') {
3157 part1.length < ch.length &&
3158 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
3159 part1 = ch.substr(0, part1.length + 1);
3161 part2 = ch.substr(part1.length);
3162 if (part1.length > 2) {
3165 } else if (start == '\u001BO') {
3167 part2 = ch.substr(2);
3169 if (part1 != undefined) {
3171 ((event.shiftKey ? 1 : 0) +
3172 (event.altKey|event.metaKey ? 2 : 0) +
3173 (event.ctrlKey ? 4 : 0)) +
3175 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
3180 if (this.menu.style.visibility == 'hidden') {
3181 // this.vt100('R: c=');
3182 // for (var i = 0; i < ch.length; i++)
3183 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
3184 // this.vt100('\r\n');
3185 this.keysPressed(ch);
3189 VT100.prototype.inspect = function(o, d) {
3190 if (d == undefined) {
3194 if (typeof o == 'object' && ++d < 2) {
3197 rc += this.spaces(d * 2) + i + ' -> ';
3199 rc += this.inspect(o[i], d);
3201 rc += '?' + '?' + '?\r\n';
3206 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
3211 VT100.prototype.checkComposedKeys = function(event) {
3212 // Composed keys (at least on Linux) do not generate normal events.
3213 // Instead, they get entered into the text field. We normally catch
3214 // this on the next keyup event.
3215 var s = this.input.value;
3217 this.input.value = '';
3218 if (this.menu.style.visibility == 'hidden') {
3219 this.keysPressed(s);
3224 VT100.prototype.fixEvent = function(event) {
3225 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
3226 // is used as a second-level selector, clear the modifier bits before
3227 // handling the event.
3228 if (event.ctrlKey && event.altKey) {
3230 fake.charCode = event.charCode;
3231 fake.keyCode = event.keyCode;
3232 fake.ctrlKey = false;
3233 fake.shiftKey = event.shiftKey;
3234 fake.altKey = false;
3235 fake.metaKey = event.metaKey;
3239 // Some browsers fail to translate keys, if both shift and alt/meta is
3240 // pressed at the same time. We try to translate those cases, but that
3241 // only works for US keyboard layouts.
3242 if (event.shiftKey) {
3245 switch (this.lastNormalKeyDownEvent.keyCode) {
3246 case 39: /* ' -> " */ u = 39; s = 34; break;
3247 case 44: /* , -> < */ u = 44; s = 60; break;
3248 case 45: /* - -> _ */ u = 45; s = 95; break;
3249 case 46: /* . -> > */ u = 46; s = 62; break;
3250 case 47: /* / -> ? */ u = 47; s = 63; break;
3252 case 48: /* 0 -> ) */ u = 48; s = 41; break;
3253 case 49: /* 1 -> ! */ u = 49; s = 33; break;
3254 case 50: /* 2 -> @ */ u = 50; s = 64; break;
3255 case 51: /* 3 -> # */ u = 51; s = 35; break;
3256 case 52: /* 4 -> $ */ u = 52; s = 36; break;
3257 case 53: /* 5 -> % */ u = 53; s = 37; break;
3258 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
3259 case 55: /* 7 -> & */ u = 55; s = 38; break;
3260 case 56: /* 8 -> * */ u = 56; s = 42; break;
3261 case 57: /* 9 -> ( */ u = 57; s = 40; break;
3263 case 59: /* ; -> : */ u = 59; s = 58; break;
3264 case 61: /* = -> + */ u = 61; s = 43; break;
3265 case 91: /* [ -> { */ u = 91; s = 123; break;
3266 case 92: /* \ -> | */ u = 92; s = 124; break;
3267 case 93: /* ] -> } */ u = 93; s = 125; break;
3268 case 96: /* ` -> ~ */ u = 96; s = 126; break;
3270 case 109: /* - -> _ */ u = 45; s = 95; break;
3271 case 111: /* / -> ? */ u = 47; s = 63; break;
3273 case 186: /* ; -> : */ u = 59; s = 58; break;
3274 case 187: /* = -> + */ u = 61; s = 43; break;
3275 case 188: /* , -> < */ u = 44; s = 60; break;
3276 case 189: /* - -> _ */ u = 45; s = 95; break;
3277 case 173: /* - -> _ */ u = 45; s = 95; break; // FF15 Patch
3278 case 190: /* . -> > */ u = 46; s = 62; break;
3279 case 191: /* / -> ? */ u = 47; s = 63; break;
3280 case 192: /* ` -> ~ */ u = 96; s = 126; break;
3281 case 219: /* [ -> { */ u = 91; s = 123; break;
3282 case 220: /* \ -> | */ u = 92; s = 124; break;
3283 case 221: /* ] -> } */ u = 93; s = 125; break;
3284 case 222: /* ' -> " */ u = 39; s = 34; break;
3287 if (s && (event.charCode == u || event.charCode == 0)) {
3290 fake.keyCode = event.keyCode;
3291 fake.ctrlKey = event.ctrlKey;
3292 fake.shiftKey = event.shiftKey;
3293 fake.altKey = event.altKey;
3294 fake.metaKey = event.metaKey;
3301 VT100.prototype.keyDown = function(event) {
3302 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
3303 // (event.shiftKey || event.ctrlKey || event.altKey ||
3304 // event.metaKey ? ', ' +
3305 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3306 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3308 this.checkComposedKeys(event);
3309 this.lastKeyPressedEvent = undefined;
3310 this.lastKeyDownEvent = undefined;
3311 this.lastNormalKeyDownEvent = event;
3313 // Swiss keyboard conflicts:
3320 // French keyoard conflicts:
3324 event.keyCode == 32 ||
3325 event.keyCode >= 48 && event.keyCode <= 57 ||
3326 event.keyCode >= 65 && event.keyCode <= 90;
3329 event.keyCode == 59 ||
3330 event.keyCode >= 96 && event.keyCode <= 105 ||
3331 event.keyCode == 107 ||
3332 event.keyCode == 192 ||
3333 event.keyCode >= 219 && event.keyCode <= 221 ||
3334 event.keyCode == 223 ||
3335 event.keyCode == 226;
3338 event.keyCode == 61 ||
3339 event.keyCode == 106 ||
3340 event.keyCode >= 109 && event.keyCode <= 111 ||
3341 event.keyCode >= 186 && event.keyCode <= 191 ||
3342 event.keyCode == 222 ||
3343 event.keyCode == 252;
3345 if (navigator.appName == 'Konqueror') {
3346 normalKey |= event.keyCode < 128;
3351 // We normally prefer to look at keypress events, as they perform the
3352 // translation from keyCode to charCode. This is important, as the
3353 // translation is locale-dependent.
3354 // But for some keys, we must intercept them during the keydown event,
3355 // as they would otherwise get interpreted by the browser.
3356 // Even, when doing all of this, there are some keys that we can never
3357 // intercept. This applies to some of the menu navigation keys in IE.
3358 // In fact, we see them, but we cannot stop IE from seeing them, too.
3359 if ((event.charCode || event.keyCode) &&
3360 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3362 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3363 // interpret this sequence ourselves, as some keyboard layouts use
3364 // it for second-level layouts.
3365 !(event.ctrlKey && event.altKey)) ||
3366 this.catchModifiersEarly && normalKey && !alphNumKey &&
3367 (event.ctrlKey || event.altKey || event.metaKey) ||
3369 this.lastKeyDownEvent = event;
3371 fake.ctrlKey = event.ctrlKey;
3372 fake.shiftKey = event.shiftKey;
3373 fake.altKey = event.altKey;
3374 fake.metaKey = event.metaKey;
3376 fake.charCode = event.keyCode;
3380 fake.keyCode = event.keyCode;
3381 if (!alphNumKey && event.shiftKey) {
3382 fake = this.fixEvent(fake);
3386 this.handleKey(fake);
3387 this.lastNormalKeyDownEvent = undefined;
3390 // For non-IE browsers
3391 event.stopPropagation();
3392 event.preventDefault();
3397 event.cancelBubble = true;
3398 event.returnValue = false;
3408 VT100.prototype.keyPressed = function(event) {
3409 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3410 // (event.shiftKey || event.ctrlKey || event.altKey ||
3411 // event.metaKey ? ', ' +
3412 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3413 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3415 if (this.lastKeyDownEvent) {
3416 // If we already processed the key on keydown, do not process it
3417 // again here. Ideally, the browser should not even have generated a
3418 // keypress event in this case. But that does not appear to always work.
3419 this.lastKeyDownEvent = undefined;
3421 this.handleKey(event.altKey || event.metaKey
3422 ? this.fixEvent(event) : event);
3426 // For non-IE browsers
3427 event.preventDefault();
3433 event.cancelBubble = true;
3434 event.returnValue = false;
3439 this.lastNormalKeyDownEvent = undefined;
3440 this.lastKeyPressedEvent = event;
3444 VT100.prototype.keyUp = function(event) {
3445 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3446 // (event.shiftKey || event.ctrlKey || event.altKey ||
3447 // event.metaKey ? ', ' +
3448 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3449 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3451 if (this.lastKeyPressedEvent) {
3452 // The compose key on Linux occasionally confuses the browser and keeps
3453 // inserting bogus characters into the input field, even if just a regular
3454 // key has been pressed. Detect this case and drop the bogus characters.
3456 event.srcElement).value = '';
3458 // This is usually were we notice that a key has been composed and
3459 // thus failed to generate normal events.
3460 this.checkComposedKeys(event);
3462 // Some browsers don't report keypress events if ctrl or alt is pressed
3463 // for non-alphanumerical keys. Patch things up for now, but in the
3464 // future we will catch these keys earlier (in the keydown handler).
3465 if (this.lastNormalKeyDownEvent) {
3466 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3467 this.catchModifiersEarly = true;
3469 event.keyCode == 32 ||
3470 // Conflicts with dead key ~ (code 50) on French keyboards
3471 //event.keyCode >= 48 && event.keyCode <= 57 ||
3472 event.keyCode >= 48 && event.keyCode <= 49 ||
3473 event.keyCode >= 51 && event.keyCode <= 57 ||
3474 event.keyCode >= 65 && event.keyCode <= 90;
3477 event.keyCode == 50 ||
3478 event.keyCode >= 96 && event.keyCode <= 105;
3481 event.keyCode == 59 || event.keyCode == 61 ||
3482 event.keyCode == 106 || event.keyCode == 107 ||
3483 event.keyCode >= 109 && event.keyCode <= 111 ||
3484 event.keyCode >= 186 && event.keyCode <= 192 ||
3485 event.keyCode >= 219 && event.keyCode <= 223 ||
3486 event.keyCode == 252;
3488 fake.ctrlKey = event.ctrlKey;
3489 fake.shiftKey = event.shiftKey;
3490 fake.altKey = event.altKey;
3491 fake.metaKey = event.metaKey;
3493 fake.charCode = event.keyCode;
3497 fake.keyCode = event.keyCode;
3498 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3499 fake = this.fixEvent(fake);
3502 this.lastNormalKeyDownEvent = undefined;
3503 this.handleKey(fake);
3509 event.cancelBubble = true;
3510 event.returnValue = false;
3515 this.lastKeyDownEvent = undefined;
3516 this.lastKeyPressedEvent = undefined;
3520 VT100.prototype.animateCursor = function(inactive) {
3521 if (!this.cursorInterval) {
3522 this.cursorInterval = setInterval(
3525 vt100.animateCursor();
3527 // Use this opportunity to check whether the user entered a composed
3528 // key, or whether somebody pasted text into the textfield.
3529 vt100.checkComposedKeys();
3533 if (inactive != undefined || this.cursor.className != 'inactive') {
3535 this.cursor.className = 'inactive';
3537 if (this.blinkingCursor) {
3538 this.cursor.className = this.cursor.className == 'bright'
3541 this.cursor.className = 'bright';
3547 VT100.prototype.blurCursor = function() {
3548 this.animateCursor(true);
3551 VT100.prototype.focusCursor = function() {
3552 this.animateCursor(false);
3555 VT100.prototype.flashScreen = function() {
3556 this.isInverted = !this.isInverted;
3557 this.refreshInvertedState();
3558 this.isInverted = !this.isInverted;
3559 setTimeout(function(vt100) {
3561 vt100.refreshInvertedState();
3566 VT100.prototype.beep = function() {
3567 if (this.visualBell) {
3574 this.beeper.src = 'beep.wav';
3581 VT100.prototype.bs = function() {
3582 if (this.cursorX > 0) {
3583 this.gotoXY(this.cursorX - 1, this.cursorY);
3584 this.needWrap = false;
3588 VT100.prototype.ht = function(count) {
3589 if (count == undefined) {
3592 var cx = this.cursorX;
3593 while (count-- > 0) {
3594 while (cx++ < this.terminalWidth) {
3595 var tabState = this.userTabStop[cx];
3596 if (tabState == false) {
3597 // Explicitly cleared tab stop
3599 } else if (tabState) {
3600 // Explicitly set tab stop
3603 // Default tab stop at each eighth column
3610 if (cx > this.terminalWidth - 1) {
3611 cx = this.terminalWidth - 1;
3613 if (cx != this.cursorX) {
3614 this.gotoXY(cx, this.cursorY);
3618 VT100.prototype.rt = function(count) {
3619 if (count == undefined) {
3622 var cx = this.cursorX;
3623 while (count-- > 0) {
3625 var tabState = this.userTabStop[cx];
3626 if (tabState == false) {
3627 // Explicitly cleared tab stop
3629 } else if (tabState) {
3630 // Explicitly set tab stop
3633 // Default tab stop at each eighth column
3643 if (cx != this.cursorX) {
3644 this.gotoXY(cx, this.cursorY);
3648 VT100.prototype.cr = function() {
3649 this.gotoXY(0, this.cursorY);
3650 this.needWrap = false;
3653 VT100.prototype.lf = function(count) {
3654 if (count == undefined) {
3657 if (count > this.terminalHeight) {
3658 count = this.terminalHeight;
3664 while (count-- > 0) {
3665 if (this.cursorY == this.bottom - 1) {
3666 this.scrollRegion(0, this.top + 1,
3667 this.terminalWidth, this.bottom - this.top - 1,
3668 0, -1, this.color, this.style);
3670 } else if (this.cursorY < this.terminalHeight - 1) {
3671 this.gotoXY(this.cursorX, this.cursorY + 1);
3676 VT100.prototype.ri = function(count) {
3677 if (count == undefined) {
3680 if (count > this.terminalHeight) {
3681 count = this.terminalHeight;
3687 while (count-- > 0) {
3688 if (this.cursorY == this.top) {
3689 this.scrollRegion(0, this.top,
3690 this.terminalWidth, this.bottom - this.top - 1,
3691 0, 1, this.color, this.style);
3692 } else if (this.cursorY > 0) {
3693 this.gotoXY(this.cursorX, this.cursorY - 1);
3696 this.needWrap = false;
3699 VT100.prototype.respondID = function() {
3700 this.respondString += '\u001B[?6c';
3703 VT100.prototype.respondSecondaryDA = function() {
3704 this.respondString += '\u001B[>0;0;0c';
3708 VT100.prototype.updateStyle = function() {
3710 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3711 this.style = 'text-decoration: underline;';
3713 var bg = (this.attr >> 4) & 0xF;
3714 var fg = this.attr & 0xF;
3715 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3720 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3721 fg = 8; // Dark grey
3722 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3724 this.style = 'font-weight: bold;';
3726 if (this.attr & 0x1000 /* ATTR_BLINK */) {
3727 this.style = 'text-decoration: blink;';
3729 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3732 VT100.prototype.setAttrColors = function(attr) {
3733 if (attr != this.attr) {
3739 VT100.prototype.saveCursor = function() {
3740 this.savedX[this.currentScreen] = this.cursorX;
3741 this.savedY[this.currentScreen] = this.cursorY;
3742 this.savedAttr[this.currentScreen] = this.attr;
3743 this.savedUseGMap = this.useGMap;
3744 for (var i = 0; i < 4; i++) {
3745 this.savedGMap[i] = this.GMap[i];
3747 this.savedValid[this.currentScreen] = true;
3750 VT100.prototype.restoreCursor = function() {
3751 if (!this.savedValid[this.currentScreen]) {
3754 this.attr = this.savedAttr[this.currentScreen];
3756 this.useGMap = this.savedUseGMap;
3757 for (var i = 0; i < 4; i++) {
3758 this.GMap[i] = this.savedGMap[i];
3760 this.translate = this.GMap[this.useGMap];
3761 this.needWrap = false;
3762 this.gotoXY(this.savedX[this.currentScreen],
3763 this.savedY[this.currentScreen]);
3766 VT100.prototype.getTransformName = function() {
3767 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3768 for (var i = 0; i < styles.length; ++i) {
3769 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3776 VT100.prototype.getTransformStyle = function(transform, scale) {
3777 return scale && scale != 1.0
3778 ? transform == 'filter'
3779 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3780 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3781 "sizingMethod='auto expand')"
3782 : 'translateX(-50%) ' +
3783 'scaleX(' + (1.0/scale) + ') ' +
3788 VT100.prototype.set80_132Mode = function(state) {
3789 var transform = this.getTransformName();
3791 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3795 this.getTransformStyle(transform, 1.65):'';
3796 this.console[this.currentScreen].style[transform] = style;
3797 this.cursor.style[transform] = style;
3798 this.space.style[transform] = style;
3799 this.scale = state ? 1.65 : 1.0;
3800 if (transform == 'filter') {
3801 this.console[this.currentScreen].style.width = state ? '165%' : '';
3807 VT100.prototype.setMode = function(state) {
3808 for (var i = 0; i <= this.npar; i++) {
3809 if (this.isQuestionMark) {
3810 switch (this.par[i]) {
3811 case 1: this.cursorKeyMode = state; break;
3812 case 3: this.set80_132Mode(state); break;
3813 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3814 case 6: this.offsetMode = state; break;
3815 case 7: this.autoWrapMode = state; break;
3817 case 9: this.mouseReporting = state; break;
3818 case 25: this.cursorNeedsShowing = state;
3819 if (state) { this.showCursor(); }
3820 else { this.hideCursor(); } break;
3823 case 47: this.enableAlternateScreen(state); break;
3827 switch (this.par[i]) {
3828 case 3: this.dispCtrl = state; break;
3829 case 4: this.insertMode = state; break;
3830 case 20:this.crLfMode = state; break;
3837 VT100.prototype.statusReport = function() {
3838 // Ready and operational.
3839 this.respondString += '\u001B[0n';
3842 VT100.prototype.cursorReport = function() {
3843 this.respondString += '\u001B[' +
3844 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3846 (this.cursorX + 1) +
3850 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3851 // Changing of cursor color is not implemented.
3854 VT100.prototype.openPrinterWindow = function() {
3857 if (!this.printWin || this.printWin.closed) {
3858 this.printWin = window.open('', 'print-output',
3859 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3860 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3861 this.printWin.document.body.innerHTML =
3862 '<link rel="stylesheet" href="' +
3863 document.location.protocol + '//' + document.location.host +
3864 document.location.pathname.replace(/[^/]*$/, '') +
3865 'print-styles.css" type="text/css">\n' +
3866 '<div id="options"><input id="autoprint" type="checkbox"' +
3867 (this.autoprint ? ' checked' : '') + '>' +
3868 'Automatically, print page(s) when job is ready' +
3869 '</input></div>\n' +
3870 '<div id="spacer"><input type="checkbox"> </input></div>' +
3871 '<pre id="print"></pre>\n';
3872 var autoprint = this.printWin.document.getElementById('autoprint');
3873 this.addListener(autoprint, 'click',
3874 (function(vt100, autoprint) {
3876 vt100.autoprint = autoprint.checked;
3877 vt100.storeUserSettings();
3880 })(this, autoprint));
3881 this.printWin.document.title = 'ShellInABox Printer Output';
3884 // Maybe, a popup blocker prevented us from working. Better catch the
3885 // exception, so that we won't break the entire terminal session. The
3886 // user probably needs to disable the blocker first before retrying the
3890 rc &= this.printWin && !this.printWin.closed &&
3891 (this.printWin.innerWidth ||
3892 this.printWin.document.documentElement.clientWidth ||
3893 this.printWin.document.body.clientWidth) > 1;
3895 if (!rc && this.printing == 100) {
3896 // Different popup blockers work differently. We try to detect a couple
3897 // of common methods. And then we retry again a brief amount later, as
3898 // false positives are otherwise possible. If we are sure that there is
3899 // a popup blocker in effect, we alert the user to it. This is helpful
3900 // as some popup blockers have minimal or no UI, and the user might not
3901 // notice that they are missing the popup. In any case, we only show at
3902 // most one message per print job.
3903 this.printing = true;
3904 setTimeout((function(win) {
3906 if (!win || win.closed ||
3908 win.document.documentElement.clientWidth ||
3909 win.document.body.clientWidth) <= 1) {
3910 alert('Attempted to print, but a popup blocker ' +
3911 'prevented the printer window from opening');
3914 })(this.printWin), 2000);
3919 VT100.prototype.sendToPrinter = function(s) {
3920 this.openPrinterWindow();
3922 var doc = this.printWin.document;
3923 var print = doc.getElementById('print');
3924 if (print.lastChild && print.lastChild.nodeName == '#text') {
3925 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3927 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3930 // There probably was a more aggressive popup blocker that prevented us
3931 // from accessing the printer windows.
3935 VT100.prototype.sendControlToPrinter = function(ch) {
3936 // We get called whenever doControl() is active. But for the printer, we
3937 // only implement a basic line printer that doesn't understand most of
3938 // the escape sequences of the VT100 terminal. In fact, the only escape
3939 // sequence that we really need to recognize is '^[[5i' for turning the
3945 this.openPrinterWindow();
3946 var doc = this.printWin.document;
3947 var print = doc.getElementById('print');
3948 var chars = print.lastChild &&
3949 print.lastChild.nodeName == '#text' ?
3950 print.lastChild.textContent.length : 0;
3951 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3958 this.openPrinterWindow();
3959 var pageBreak = this.printWin.document.createElement('div');
3960 pageBreak.className = 'pagebreak';
3961 pageBreak.innerHTML = '<hr />';
3962 this.printWin.document.getElementById('print').appendChild(pageBreak);
3966 this.openPrinterWindow();
3967 var lineBreak = this.printWin.document.createElement('br');
3968 this.printWin.document.getElementById('print').appendChild(lineBreak);
3972 this.isEsc = 1 /* ESesc */;
3975 switch (this.isEsc) {
3977 this.isEsc = 0 /* ESnormal */;
3980 this.isEsc = 2 /* ESsquare */;
3986 case 2 /* ESsquare */:
3988 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3989 0, 0, 0, 0, 0, 0, 0, 0 ];
3990 this.isEsc = 3 /* ESgetpars */;
3991 this.isQuestionMark = ch == 0x3F /*?*/;
3992 if (this.isQuestionMark) {
3996 case 3 /* ESgetpars */:
3997 if (ch == 0x3B /*;*/) {
4000 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4001 var par = this.par[this.npar];
4002 if (par == undefined) {
4005 this.par[this.npar] = 10*par + (ch & 0xF);
4008 this.isEsc = 4 /* ESgotpars */;
4011 case 4 /* ESgotpars */:
4012 this.isEsc = 0 /* ESnormal */;
4013 if (this.isQuestionMark) {
4018 this.csii(this.par[0]);
4025 this.isEsc = 0 /* ESnormal */;
4031 // There probably was a more aggressive popup blocker that prevented us
4032 // from accessing the printer windows.
4036 VT100.prototype.csiAt = function(number) {
4041 if (number > this.terminalWidth - this.cursorX) {
4042 number = this.terminalWidth - this.cursorX;
4044 this.scrollRegion(this.cursorX, this.cursorY,
4045 this.terminalWidth - this.cursorX - number, 1,
4046 number, 0, this.color, this.style);
4047 this.needWrap = false;
4050 VT100.prototype.csii = function(number) {
4053 case 0: // Print Screen
4056 case 4: // Stop printing
4058 if (this.printing && this.printWin && !this.printWin.closed) {
4059 var print = this.printWin.document.getElementById('print');
4060 while (print.lastChild &&
4061 print.lastChild.tagName == 'DIV' &&
4062 print.lastChild.className == 'pagebreak') {
4063 // Remove trailing blank pages
4064 print.removeChild(print.lastChild);
4066 if (this.autoprint) {
4067 this.printWin.print();
4072 this.printing = false;
4074 case 5: // Start printing
4075 if (!this.printing && this.printWin && !this.printWin.closed) {
4076 this.printWin.document.getElementById('print').innerHTML = '';
4078 this.printing = 100;
4085 VT100.prototype.csiJ = function(number) {
4087 case 0: // Erase from cursor to end of display
4088 this.clearRegion(this.cursorX, this.cursorY,
4089 this.terminalWidth - this.cursorX, 1,
4090 this.color, this.style);
4091 if (this.cursorY < this.terminalHeight-2) {
4092 this.clearRegion(0, this.cursorY+1,
4093 this.terminalWidth, this.terminalHeight-this.cursorY-1,
4094 this.color, this.style);
4097 case 1: // Erase from start to cursor
4098 if (this.cursorY > 0) {
4099 this.clearRegion(0, 0,
4100 this.terminalWidth, this.cursorY,
4101 this.color, this.style);
4103 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4104 this.color, this.style);
4106 case 2: // Erase whole display
4107 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
4108 this.color, this.style);
4116 VT100.prototype.csiK = function(number) {
4118 case 0: // Erase from cursor to end of line
4119 this.clearRegion(this.cursorX, this.cursorY,
4120 this.terminalWidth - this.cursorX, 1,
4121 this.color, this.style);
4123 case 1: // Erase from start of line to cursor
4124 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4125 this.color, this.style);
4127 case 2: // Erase whole line
4128 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
4129 this.color, this.style);
4137 VT100.prototype.csiL = function(number) {
4138 // Open line by inserting blank line(s)
4139 if (this.cursorY >= this.bottom) {
4145 if (number > this.bottom - this.cursorY) {
4146 number = this.bottom - this.cursorY;
4148 this.scrollRegion(0, this.cursorY,
4149 this.terminalWidth, this.bottom - this.cursorY - number,
4150 0, number, this.color, this.style);
4154 VT100.prototype.csiM = function(number) {
4155 // Delete line(s), scrolling up the bottom of the screen.
4156 if (this.cursorY >= this.bottom) {
4162 if (number > this.bottom - this.cursorY) {
4163 number = bottom - cursorY;
4165 this.scrollRegion(0, this.cursorY + number,
4166 this.terminalWidth, this.bottom - this.cursorY - number,
4167 0, -number, this.color, this.style);
4171 VT100.prototype.csim = function() {
4172 for (var i = 0; i <= this.npar; i++) {
4173 switch (this.par[i]) {
4174 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
4175 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
4176 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
4177 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
4178 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
4179 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
4181 this.translate = this.GMap[this.useGMap];
4182 this.dispCtrl = false;
4183 this.toggleMeta = false;
4186 this.translate = this.CodePage437Map;
4187 this.dispCtrl = true;
4188 this.toggleMeta = false;
4191 this.translate = this.CodePage437Map;
4192 this.dispCtrl = true;
4193 this.toggleMeta = true;
4196 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
4197 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
4198 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
4199 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
4200 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
4201 0x0200 /* ATTR_UNDERLINE */; break;
4202 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
4203 case 49: this.attr |= 0xF0; break;
4205 if (this.par[i] >= 30 && this.par[i] <= 37) {
4206 var fg = this.par[i] - 30;
4207 this.attr = (this.attr & ~0x0F) | fg;
4208 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
4209 var bg = this.par[i] - 40;
4210 this.attr = (this.attr & ~0xF0) | (bg << 4);
4218 VT100.prototype.csiP = function(number) {
4219 // Delete character(s) following cursor
4223 if (number > this.terminalWidth - this.cursorX) {
4224 number = this.terminalWidth - this.cursorX;
4226 this.scrollRegion(this.cursorX + number, this.cursorY,
4227 this.terminalWidth - this.cursorX - number, 1,
4228 -number, 0, this.color, this.style);
4232 VT100.prototype.csiX = function(number) {
4233 // Clear characters following cursor
4237 if (number > this.terminalWidth - this.cursorX) {
4238 number = this.terminalWidth - this.cursorX;
4240 this.clearRegion(this.cursorX, this.cursorY, number, 1,
4241 this.color, this.style);
4245 VT100.prototype.settermCommand = function() {
4246 // Setterm commands are not implemented
4249 VT100.prototype.doControl = function(ch) {
4250 if (this.printing) {
4251 this.sendControlToPrinter(ch);
4256 case 0x00: /* ignored */ break;
4257 case 0x08: this.bs(); break;
4258 case 0x09: this.ht(); break;
4262 case 0x84: this.lf(); if (!this.crLfMode) break;
4263 case 0x0D: this.cr(); break;
4264 case 0x85: this.cr(); this.lf(); break;
4265 case 0x0E: this.useGMap = 1;
4266 this.translate = this.GMap[1];
4267 this.dispCtrl = true; break;
4268 case 0x0F: this.useGMap = 0;
4269 this.translate = this.GMap[0];
4270 this.dispCtrl = false; break;
4272 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
4273 case 0x1B: this.isEsc = 1 /* ESesc */; break;
4274 case 0x7F: /* ignored */ break;
4275 case 0x88: this.userTabStop[this.cursorX] = true; break;
4276 case 0x8D: this.ri(); break;
4277 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
4278 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
4279 case 0x9A: this.respondID(); break;
4280 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
4281 case 0x07: if (this.isEsc != 17 /* EStitle */) {
4285 default: switch (this.isEsc) {
4287 this.isEsc = 0 /* ESnormal */;
4289 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
4290 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
4292 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
4294 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
4296 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
4297 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
4298 /*7*/ case 0x37: this.saveCursor(); break;
4299 /*8*/ case 0x38: this.restoreCursor(); break;
4300 /*>*/ case 0x3E: this.applKeyMode = false; break;
4301 /*=*/ case 0x3D: this.applKeyMode = true; break;
4302 /*D*/ case 0x44: this.lf(); break;
4303 /*E*/ case 0x45: this.cr(); this.lf(); break;
4304 /*M*/ case 0x4D: this.ri(); break;
4305 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
4306 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
4307 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
4308 /*Z*/ case 0x5A: this.respondID(); break;
4309 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
4310 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
4311 /*c*/ case 0x63: this.reset(); break;
4312 /*g*/ case 0x67: this.flashScreen(); break;
4316 case 15 /* ESnonstd */:
4320 /*2*/ case 0x32: this.isEsc = 17 /* EStitle */; this.titleString = ''; break;
4321 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4322 this.isEsc = 16 /* ESpalette */; break;
4323 /*R*/ case 0x52: // Palette support is not implemented
4324 this.isEsc = 0 /* ESnormal */; break;
4325 default: this.isEsc = 0 /* ESnormal */; break;
4328 case 16 /* ESpalette */:
4329 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4330 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4331 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4332 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
4334 if (this.npar == 7) {
4335 // Palette support is not implemented
4336 this.isEsc = 0 /* ESnormal */;
4339 this.isEsc = 0 /* ESnormal */;
4342 case 2 /* ESsquare */:
4344 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
4345 0, 0, 0, 0, 0, 0, 0, 0 ];
4346 this.isEsc = 3 /* ESgetpars */;
4347 /*[*/ if (ch == 0x5B) { // Function key
4348 this.isEsc = 6 /* ESfunckey */;
4351 /*?*/ this.isQuestionMark = ch == 0x3F;
4352 if (this.isQuestionMark) {
4357 case 5 /* ESdeviceattr */:
4358 case 3 /* ESgetpars */:
4359 /*;*/ if (ch == 0x3B) {
4362 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4363 var par = this.par[this.npar];
4364 if (par == undefined) {
4367 this.par[this.npar] = 10*par + (ch & 0xF);
4369 } else if (this.isEsc == 5 /* ESdeviceattr */) {
4371 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
4372 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
4373 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
4374 /*p*/ case 0x70: /* set pointer mode resource value */ break;
4377 this.isEsc = 0 /* ESnormal */;
4380 this.isEsc = 4 /* ESgotpars */;
4383 case 4 /* ESgotpars */:
4384 this.isEsc = 0 /* ESnormal */;
4385 if (this.isQuestionMark) {
4387 /*h*/ case 0x68: this.setMode(true); break;
4388 /*l*/ case 0x6C: this.setMode(false); break;
4389 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
4392 this.isQuestionMark = false;
4396 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
4397 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
4399 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
4400 /*A*/ case 0x41: this.gotoXY(this.cursorX,
4401 this.cursorY - (this.par[0] ? this.par[0] : 1));
4404 /*e*/ case 0x65: this.gotoXY(this.cursorX,
4405 this.cursorY + (this.par[0] ? this.par[0] : 1));
4408 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4409 this.cursorY); break;
4410 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4411 this.cursorY); break;
4412 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4414 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4416 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
4418 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
4419 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
4420 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
4421 /*i*/ case 0x69: this.csii(this.par[0]); break;
4422 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
4423 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
4424 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
4425 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
4426 /*m*/ case 0x6D: this.csim(); break;
4427 /*P*/ case 0x50: this.csiP(this.par[0]); break;
4428 /*X*/ case 0x58: this.csiX(this.par[0]); break;
4429 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4430 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4431 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4432 /*g*/ case 0x67: if (this.par[0] == 0) {
4433 this.userTabStop[this.cursorX] = false;
4434 } else if (this.par[0] == 2 || this.par[0] == 3) {
4435 this.userTabStop = [ ];
4436 for (var i = 0; i < this.terminalWidth; i++) {
4437 this.userTabStop[i] = false;
4441 /*h*/ case 0x68: this.setMode(true); break;
4442 /*l*/ case 0x6C: this.setMode(false); break;
4443 /*n*/ case 0x6E: switch (this.par[0]) {
4444 case 5: this.statusReport(); break;
4445 case 6: this.cursorReport(); break;
4449 /*q*/ case 0x71: // LED control not implemented
4451 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4452 var b = this.par[1] ? this.par[1]
4453 : this.terminalHeight;
4454 if (t < b && b <= this.terminalHeight) {
4460 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4461 if (c > this.terminalWidth * this.terminalHeight) {
4462 c = this.terminalWidth * this.terminalHeight;
4465 lineBuf += this.lastCharacter;
4468 /*s*/ case 0x73: this.saveCursor(); break;
4469 /*u*/ case 0x75: this.restoreCursor(); break;
4470 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4471 /*]*/ case 0x5D: this.settermCommand(); break;
4475 case 12 /* ESbang */:
4479 this.isEsc = 0 /* ESnormal */;
4481 case 13 /* ESpercent */:
4482 this.isEsc = 0 /* ESnormal */;
4484 /*@*/ case 0x40: this.utfEnabled = false; break;
4486 /*8*/ case 0x38: this.utfEnabled = true; break;
4490 case 6 /* ESfunckey */:
4491 this.isEsc = 0 /* ESnormal */; break;
4492 case 7 /* EShash */:
4493 this.isEsc = 0 /* ESnormal */;
4494 /*8*/ if (ch == 0x38) {
4495 // Screen alignment test not implemented
4498 case 8 /* ESsetG0 */:
4499 case 9 /* ESsetG1 */:
4500 case 10 /* ESsetG2 */:
4501 case 11 /* ESsetG3 */:
4502 var g = this.isEsc - 8 /* ESsetG0 */;
4503 this.isEsc = 0 /* ESnormal */;
4505 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4507 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4508 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4509 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4512 if (this.useGMap == g) {
4513 this.translate = this.GMap[g];
4516 case 17 /* EStitle */:
4518 if (this.titleString && this.titleString.charAt(0) == ';') {
4519 this.titleString = this.titleString.substr(1);
4520 if (this.titleString != '') {
4521 this.titleString += ' - ';
4523 this.titleString += 'Shell In A Box'
4526 window.document.title = this.titleString;
4529 this.isEsc = 0 /* ESnormal */;
4531 this.titleString += String.fromCharCode(ch);
4534 case 18 /* ESss2 */:
4535 case 19 /* ESss3 */:
4537 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4538 [this.toggleMeta ? (ch | 0x80) : ch];
4539 if ((ch & 0xFF00) == 0xF000) {
4541 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4542 this.isEsc = 0 /* ESnormal */; break;
4545 this.lastCharacter = String.fromCharCode(ch);
4546 lineBuf += this.lastCharacter;
4547 this.isEsc = 0 /* ESnormal */; break;
4549 this.isEsc = 0 /* ESnormal */; break;
4556 VT100.prototype.renderString = function(s, showCursor) {
4557 if (this.printing) {
4558 this.sendToPrinter(s);
4565 // We try to minimize the number of DOM operations by coalescing individual
4566 // characters into strings. This is a significant performance improvement.
4567 var incX = s.length;
4568 if (incX > this.terminalWidth - this.cursorX) {
4569 incX = this.terminalWidth - this.cursorX;
4573 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4576 // Minimize the number of calls to putString(), by avoiding a direct
4577 // call to this.showCursor()
4578 this.cursor.style.visibility = '';
4580 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4583 VT100.prototype.vt100 = function(s) {
4584 this.cursorNeedsShowing = this.hideCursor();
4585 this.respondString = '';
4587 for (var i = 0; i < s.length; i++) {
4588 var ch = s.charCodeAt(i);
4589 if (this.utfEnabled) {
4590 // Decode UTF8 encoded character
4592 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4593 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4594 if (--this.utfCount <= 0) {
4595 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4604 if ((ch & 0xE0) == 0xC0) {
4606 this.utfChar = ch & 0x1F;
4607 } else if ((ch & 0xF0) == 0xE0) {
4609 this.utfChar = ch & 0x0F;
4610 } else if ((ch & 0xF8) == 0xF0) {
4612 this.utfChar = ch & 0x07;
4613 } else if ((ch & 0xFC) == 0xF8) {
4615 this.utfChar = ch & 0x03;
4616 } else if ((ch & 0xFE) == 0xFC) {
4618 this.utfChar = ch & 0x01;
4628 var isNormalCharacter =
4629 (ch >= 32 && ch <= 127 || ch >= 160 ||
4630 this.utfEnabled && ch >= 128 ||
4631 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4632 (ch != 0x7F || this.dispCtrl);
4634 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4636 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4638 if ((ch & 0xFF00) == 0xF000) {
4640 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4643 if (!this.printing) {
4644 if (this.needWrap || this.insertMode) {
4646 this.renderString(lineBuf);
4650 if (this.needWrap) {
4651 this.cr(); this.lf();
4653 if (this.insertMode) {
4654 this.scrollRegion(this.cursorX, this.cursorY,
4655 this.terminalWidth - this.cursorX - 1, 1,
4656 1, 0, this.color, this.style);
4659 this.lastCharacter = String.fromCharCode(ch);
4660 lineBuf += this.lastCharacter;
4661 if (!this.printing &&
4662 this.cursorX + lineBuf.length >= this.terminalWidth) {
4663 this.needWrap = this.autoWrapMode;
4667 this.renderString(lineBuf);
4670 var expand = this.doControl(ch);
4671 if (expand.length) {
4672 var r = this.respondString;
4673 this.respondString= r + this.vt100(expand);
4678 this.renderString(lineBuf, this.cursorNeedsShowing);
4679 } else if (this.cursorNeedsShowing) {
4682 return this.respondString;
4685 VT100.prototype.Latin1Map = [
4686 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4687 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4688 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4689 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4690 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4691 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4692 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4693 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4694 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4695 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4696 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4697 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4698 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4699 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4700 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4701 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4702 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4703 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4704 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4705 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4706 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4707 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4708 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4709 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4710 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4711 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4712 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4713 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4714 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4715 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4716 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4717 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4720 VT100.prototype.VT100GraphicsMap = [
4721 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4722 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4723 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4724 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4725 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4726 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4727 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4728 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4729 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4730 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4731 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4732 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4733 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4734 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4735 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4736 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4737 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4738 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4739 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4740 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4741 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4742 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4743 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4744 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4745 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4746 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4747 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4748 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4749 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4750 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4751 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4752 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4755 VT100.prototype.CodePage437Map = [
4756 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4757 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4758 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4759 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4760 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4761 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4762 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4763 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4764 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4765 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4766 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4767 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4768 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4769 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4770 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4771 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4772 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4773 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4774 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4775 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4776 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4777 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4778 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4779 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4780 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4781 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4782 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4783 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4784 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4785 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4786 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4787 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4790 VT100.prototype.DirectToFontMap = [
4791 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4792 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4793 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4794 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4795 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4796 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4797 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4798 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4799 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4800 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4801 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4802 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4803 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4804 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4805 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4806 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4807 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4808 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4809 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4810 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4811 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4812 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4813 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4814 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4815 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4816 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4817 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4818 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4819 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4820 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4821 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4822 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4825 VT100.prototype.ctrlAction = [
4826 true, false, false, false, false, false, false, true,
4827 true, true, true, true, true, true, true, true,
4828 false, false, false, false, false, false, false, false,
4829 true, false, true, true, false, false, false, false
4832 VT100.prototype.ctrlAlways = [
4833 true, false, false, false, false, false, false, false,
4834 true, false, true, false, true, true, true, true,
4835 false, false, false, false, false, false, false, false,
4836 false, false, false, true, false, false, false, false