1 // This file contains code from shell_in_a_box.js and vt100.js
4 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
5 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2 as
9 // published by the Free Software Foundation.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // In addition to these license terms, the author grants the following
23 // If you modify this program, or any covered work, by linking or
24 // combining it with the OpenSSL project's OpenSSL library (or a
25 // modified version of that library), containing parts covered by the
26 // terms of the OpenSSL or SSLeay licenses, the author
27 // grants you additional permission to convey the resulting work.
28 // Corresponding Source for a non-source form of such a combination
29 // shall include the source code for the parts of OpenSSL used as well
30 // as that of the covered work.
32 // You may at your option choose to remove this additional permission from
33 // the work, or from any part of it.
35 // It is possible to build this program in a way that it loads OpenSSL
36 // libraries at run-time. If doing so, the following notices are required
37 // by the OpenSSL and SSLeay licenses:
39 // This product includes software developed by the OpenSSL Project
40 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
42 // This product includes cryptographic software written by Eric Young
43 // (eay@cryptsoft.com)
46 // The most up-to-date version of this program is always available from
47 // http://shellinabox.com
52 // The author believes that for the purposes of this license, you meet the
53 // requirements for publishing the source code, if your web server publishes
54 // the source in unmodified form (i.e. with licensing information, comments,
55 // formatting, and identifier names intact). If there are technical reasons
56 // that require you to make changes to the source code when serving the
57 // JavaScript (e.g to remove pre-processor directives from the source), these
58 // changes should be done in a reversible fashion.
60 // The author does not consider websites that reference this script in
61 // unmodified form, and web servers that serve this script in unmodified form
62 // to be derived works. As such, they are believed to be outside of the
63 // scope of this license and not subject to the rights or restrictions of the
64 // GNU General Public License.
66 // If in doubt, consult a legal professional familiar with the laws that
67 // apply in your country.
69 // #define XHR_UNITIALIZED 0
72 // #define XHR_RECEIVING 3
73 // #define XHR_LOADED 4
75 // IE does not define XMLHttpRequest by default, so we provide a suitable
77 if (typeof XMLHttpRequest == 'undefined') {
78 XMLHttpRequest = function() {
79 try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
80 try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
81 try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { }
82 try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
87 function extend(subClass, baseClass) {
88 function inheritance() { }
89 inheritance.prototype = baseClass.prototype;
90 subClass.prototype = new inheritance();
91 subClass.prototype.constructor = subClass;
92 subClass.prototype.superClass = baseClass.prototype;
95 function ShellInABox(url, container) {
96 if (url == undefined) {
97 this.rooturl = document.location.href;
98 this.url = document.location.href.replace(/[?#].*/, '');
103 if (document.location.hash != '') {
104 var hash = decodeURIComponent(document.location.hash).
106 this.nextUrl = hash.replace(/,.*/, '');
107 this.session = hash.replace(/[^,]*,/, '');
109 this.nextUrl = this.url;
112 this.pendingKeys = '';
113 this.keysInFlight = false;
114 this.connected = false;
115 this.superClass.constructor.call(this, container);
117 // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
118 // Chrome never realizes that the page has loaded.
119 setTimeout(function(shellInABox) {
121 shellInABox.sendRequest();
125 extend(ShellInABox, VT100);
127 ShellInABox.prototype.sessionClosed = function() {
129 this.connected = false;
131 this.session = undefined;
132 if (this.cursorX > 0) {
135 this.vt100('Session closed.');
137 // Revealing the "reconnect" button is commented out until we hook
138 // up the username+token auto-login mechanism to the new session:
139 //this.showReconnect(true);
144 ShellInABox.prototype.reconnect = function() {
145 this.showReconnect(false);
147 if (document.location.hash != '') {
148 // A shellinaboxd daemon launched from a CGI only allows a single
149 // session. In order to reconnect, we must reload the frame definition
150 // and obtain a new port number. As this is a different origin, we
151 // need to get enclosing page to help us.
152 parent.location = this.nextUrl;
154 if (this.url != this.nextUrl) {
155 document.location.replace(this.nextUrl);
157 this.pendingKeys = '';
158 this.keysInFlight = false;
167 ShellInABox.prototype.sendRequest = function(request) {
168 if (request == undefined) {
169 request = new XMLHttpRequest();
171 request.open('POST', this.url + '?', true);
172 request.setRequestHeader('Cache-Control', 'no-cache');
173 request.setRequestHeader('Content-Type',
174 'application/x-www-form-urlencoded; charset=utf-8');
175 var content = 'width=' + this.terminalWidth +
176 '&height=' + this.terminalHeight +
177 (this.session ? '&session=' +
178 encodeURIComponent(this.session) : '&rooturl='+
179 encodeURIComponent(this.rooturl));
181 request.onreadystatechange = function(shellInABox) {
184 return shellInABox.onReadyStateChange(request);
186 shellInABox.sessionClosed();
190 ShellInABox.lastRequestSent = Date.now();
191 request.send(content);
194 ShellInABox.prototype.onReadyStateChange = function(request) {
195 if (request.readyState == 4 /* XHR_LOADED */) {
196 if (request.status == 200) {
197 this.connected = true;
198 var response = eval('(' + request.responseText + ')');
200 this.vt100(response.data);
203 if (!response.session ||
204 this.session && this.session != response.session) {
205 this.sessionClosed();
207 this.session = response.session;
208 this.sendRequest(request);
210 } else if (request.status == 0) {
211 if (ShellInABox.lastRequestSent + 2000 < Date.now()) {
212 // Timeout, try again
213 this.sendRequest(request);
215 this.vt100('\r\n\r\nRequest failed.');
216 this.sessionClosed();
219 this.sessionClosed();
224 ShellInABox.prototype.sendKeys = function(keys) {
225 if (!this.connected) {
228 if (this.keysInFlight || this.session == undefined) {
229 this.pendingKeys += keys;
231 this.keysInFlight = true;
232 keys = this.pendingKeys + keys;
233 this.pendingKeys = '';
234 var request = new XMLHttpRequest();
235 request.open('POST', this.url + '?', true);
236 request.setRequestHeader('Cache-Control', 'no-cache');
237 request.setRequestHeader('Content-Type',
238 'application/x-www-form-urlencoded; charset=utf-8');
239 var content = 'width=' + this.terminalWidth +
240 '&height=' + this.terminalHeight +
241 '&session=' +encodeURIComponent(this.session)+
242 '&keys=' + encodeURIComponent(keys);
243 request.onreadystatechange = function(shellInABox) {
246 return shellInABox.keyPressReadyStateChange(request);
251 request.send(content);
255 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
256 if (request.readyState == 4 /* XHR_LOADED */) {
257 this.keysInFlight = false;
258 if (this.pendingKeys) {
264 ShellInABox.prototype.keysPressed = function(ch) {
265 var hex = '0123456789ABCDEF';
267 for (var i = 0; i < ch.length; i++) {
268 var c = ch.charCodeAt(i);
270 s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
271 } else if (c < 0x800) {
272 s += hex.charAt(0xC + (c >> 10) ) +
273 hex.charAt( (c >> 6) & 0xF ) +
274 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
275 hex.charAt( c & 0xF );
276 } else if (c < 0x10000) {
278 hex.charAt( (c >> 12) ) +
279 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
280 hex.charAt( (c >> 6) & 0xF ) +
281 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
282 hex.charAt( c & 0xF );
283 } else if (c < 0x110000) {
285 hex.charAt( (c >> 18) ) +
286 hex.charAt(0x8 + ((c >> 16) & 0x3)) +
287 hex.charAt( (c >> 12) & 0xF ) +
288 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
289 hex.charAt( (c >> 6) & 0xF ) +
290 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
291 hex.charAt( c & 0xF );
297 ShellInABox.prototype.resized = function(w, h) {
298 // Do not send a resize request until we are fully initialized.
300 // sendKeys() always transmits the current terminal size. So, flush all
306 ShellInABox.prototype.toggleSSL = function() {
307 if (document.location.hash != '') {
308 if (this.nextUrl.match(/\?plain$/)) {
309 this.nextUrl = this.nextUrl.replace(/\?plain$/, '');
311 this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain';
314 parent.location = this.nextUrl;
317 this.nextUrl = this.nextUrl.match(/^https:/)
318 ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
319 : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
321 if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
324 if (this.session && this.nextUrl != this.url) {
325 alert('This change will take effect the next time you login.');
329 ShellInABox.prototype.extendContextMenu = function(entries, actions) {
330 // Modify the entries and actions in place, adding any locally defined
332 var oldActions = [ ];
333 for (var i = 0; i < actions.length; i++) {
334 oldActions[i] = actions[i];
336 for (var node = entries.firstChild, i = 0, j = 0; node;
337 node = node.nextSibling) {
338 if (node.tagName == 'LI') {
339 actions[i++] = oldActions[j++];
340 if (node.id == "endconfig") {
342 if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
343 !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
344 // If the server supports both SSL and plain text connections,
345 // provide a menu entry to switch between the two.
346 var newNode = document.createElement('li');
348 if (document.location.hash != '') {
349 isSecure = !this.nextUrl.match(/\?plain$/);
351 isSecure = this.nextUrl.match(/^https:/);
353 newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
354 if (node.nextSibling) {
355 entries.insertBefore(newNode, node.nextSibling);
357 entries.appendChild(newNode);
359 actions[i++] = this.toggleSSL;
362 node.id = 'endconfig';
369 ShellInABox.prototype.about = function() {
370 alert("Shell In A Box version " + "2.10 (revision 239)" +
371 "\nCopyright 2008-2010 by Markus Gutschke\n" +
372 "For more information check http://shellinabox.com" +
373 (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
375 "This product includes software developed by the OpenSSL Project\n" +
376 "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" +
378 "This product includes cryptographic software written by " +
379 "Eric Young\n(eay@cryptsoft.com)" :
384 // VT100.js -- JavaScript based terminal emulator
385 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
387 // This program is free software; you can redistribute it and/or modify
388 // it under the terms of the GNU General Public License version 2 as
389 // published by the Free Software Foundation.
391 // This program is distributed in the hope that it will be useful,
392 // but WITHOUT ANY WARRANTY; without even the implied warranty of
393 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
394 // GNU General Public License for more details.
396 // You should have received a copy of the GNU General Public License along
397 // with this program; if not, write to the Free Software Foundation, Inc.,
398 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
400 // In addition to these license terms, the author grants the following
401 // additional rights:
403 // If you modify this program, or any covered work, by linking or
404 // combining it with the OpenSSL project's OpenSSL library (or a
405 // modified version of that library), containing parts covered by the
406 // terms of the OpenSSL or SSLeay licenses, the author
407 // grants you additional permission to convey the resulting work.
408 // Corresponding Source for a non-source form of such a combination
409 // shall include the source code for the parts of OpenSSL used as well
410 // as that of the covered work.
412 // You may at your option choose to remove this additional permission from
413 // the work, or from any part of it.
415 // It is possible to build this program in a way that it loads OpenSSL
416 // libraries at run-time. If doing so, the following notices are required
417 // by the OpenSSL and SSLeay licenses:
419 // This product includes software developed by the OpenSSL Project
420 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
422 // This product includes cryptographic software written by Eric Young
423 // (eay@cryptsoft.com)
426 // The most up-to-date version of this program is always available from
427 // http://shellinabox.com
432 // The author believes that for the purposes of this license, you meet the
433 // requirements for publishing the source code, if your web server publishes
434 // the source in unmodified form (i.e. with licensing information, comments,
435 // formatting, and identifier names intact). If there are technical reasons
436 // that require you to make changes to the source code when serving the
437 // JavaScript (e.g to remove pre-processor directives from the source), these
438 // changes should be done in a reversible fashion.
440 // The author does not consider websites that reference this script in
441 // unmodified form, and web servers that serve this script in unmodified form
442 // to be derived works. As such, they are believed to be outside of the
443 // scope of this license and not subject to the rights or restrictions of the
444 // GNU General Public License.
446 // If in doubt, consult a legal professional familiar with the laws that
447 // apply in your country.
449 // #define ESnormal 0
451 // #define ESsquare 2
452 // #define ESgetpars 3
453 // #define ESgotpars 4
454 // #define ESdeviceattr 5
455 // #define ESfunckey 6
459 // #define ESsetG2 10
460 // #define ESsetG3 11
462 // #define ESpercent 13
463 // #define ESignore 14
464 // #define ESnonstd 15
465 // #define ESpalette 16
466 // #define EStitle 17
470 // #define ATTR_DEFAULT 0x00F0
471 // #define ATTR_REVERSE 0x0100
472 // #define ATTR_UNDERLINE 0x0200
473 // #define ATTR_DIM 0x0400
474 // #define ATTR_BRIGHT 0x0800
475 // #define ATTR_BLINK 0x1000
477 // #define MOUSE_DOWN 0
478 // #define MOUSE_UP 1
479 // #define MOUSE_CLICK 2
481 function VT100(container) {
482 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
485 this.urlRE = new RegExp(
486 // Known URL protocol are "http", "https", and "ftp".
487 '(?:http|https|ftp)://' +
489 // Optionally allow username and passwords.
490 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
493 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
494 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
495 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
498 '(?::[1-9][0-9]*)?' +
501 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
503 (linkifyURLs <= 1 ? '' :
504 // Also support URLs without a protocol (assume "http").
505 // Optional username and password.
506 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
508 // Hostnames must end with a well-known top-level domain or must be
510 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
513 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
514 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
515 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
516 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
517 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
518 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
519 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
520 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
521 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
522 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
523 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
524 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
525 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
526 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
529 '(?::[1-9][0-9]{0,4})?' +
532 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
534 // In addition, support e-mail address. Optionally, recognize "mailto:"
535 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
538 '[-_.+a-zA-Z0-9]+@' +
541 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
542 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
543 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
544 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
545 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
546 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
547 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
548 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
549 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
550 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
551 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
552 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
553 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
554 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
556 // Optional arguments
557 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
559 this.getUserSettings();
560 this.initializeElements(container);
561 this.maxScrollbackLines = 500;
564 this.isQuestionMark = false;
567 this.savedAttr = [ ];
568 this.savedUseGMap = 0;
569 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
570 this.CodePage437Map, this.DirectToFontMap ];
571 this.savedValid = [ ];
572 this.respondString = '';
573 this.titleString = '';
574 this.internalClipboard = undefined;
578 VT100.prototype.reset = function(clearHistory) {
579 this.isEsc = 0 /* ESnormal */;
580 this.needWrap = false;
581 this.autoWrapMode = true;
582 this.dispCtrl = false;
583 this.toggleMeta = false;
584 this.insertMode = false;
585 this.applKeyMode = false;
586 this.cursorKeyMode = false;
587 this.crLfMode = false;
588 this.offsetMode = false;
589 this.mouseReporting = false;
590 this.printing = false;
591 if (typeof this.printWin != 'undefined' &&
592 this.printWin && !this.printWin.closed) {
593 this.printWin.close();
595 this.printWin = null;
596 this.utfEnabled = this.utfPreferred;
599 this.color = 'ansi0 bgAnsi15';
601 this.attr = 0x00F0 /* ATTR_DEFAULT */;
603 this.GMap = [ this.Latin1Map,
604 this.VT100GraphicsMap,
606 this.DirectToFontMap];
607 this.translate = this.GMap[this.useGMap];
609 this.bottom = this.terminalHeight;
610 this.lastCharacter = ' ';
611 this.userTabStop = [ ];
614 for (var i = 0; i < 2; i++) {
615 while (this.console[i].firstChild) {
616 this.console[i].removeChild(this.console[i].firstChild);
621 this.enableAlternateScreen(false);
623 var wasCompressed = false;
624 var transform = this.getTransformName();
626 for (var i = 0; i < 2; ++i) {
627 wasCompressed |= this.console[i].style[transform] != '';
628 this.console[i].style[transform] = '';
630 this.cursor.style[transform] = '';
631 this.space.style[transform] = '';
632 if (transform == 'filter') {
633 this.console[this.currentScreen].style.width = '';
643 this.isInverted = false;
644 this.refreshInvertedState();
645 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
646 this.color, this.style);
649 VT100.prototype.addListener = function(elem, event, listener) {
651 if (elem.addEventListener) {
652 elem.addEventListener(event, listener, false);
654 elem.attachEvent('on' + event, listener);
660 VT100.prototype.getUserSettings = function() {
661 // Compute hash signature to identify the entries in the userCSS menu.
662 // If the menu is unchanged from last time, default values can be
663 // looked up in a cookie associated with this page.
665 this.utfPreferred = true;
666 this.visualBell = typeof suppressAllAudio != 'undefined' &&
668 this.autoprint = true;
669 this.softKeyboard = false;
670 this.blinkingCursor = true;
671 if (this.visualBell) {
672 this.signature = Math.floor(16807*this.signature + 1) %
675 if (typeof userCSSList != 'undefined') {
676 for (var i = 0; i < userCSSList.length; ++i) {
677 var label = userCSSList[i][0];
678 for (var j = 0; j < label.length; ++j) {
679 this.signature = Math.floor(16807*this.signature+
680 label.charCodeAt(j)) %
683 if (userCSSList[i][1]) {
684 this.signature = Math.floor(16807*this.signature + 1) %
690 var key = 'shellInABox=' + this.signature + ':';
691 var settings = document.cookie.indexOf(key);
693 settings = document.cookie.substr(settings + key.length).
694 replace(/([0-1]*).*/, "$1");
695 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
696 0 : userCSSList.length)) {
697 this.utfPreferred = settings.charAt(0) != '0';
698 this.visualBell = settings.charAt(1) != '0';
699 this.autoprint = settings.charAt(2) != '0';
700 this.softKeyboard = settings.charAt(3) != '0';
701 this.blinkingCursor = settings.charAt(4) != '0';
702 if (typeof userCSSList != 'undefined') {
703 for (var i = 0; i < userCSSList.length; ++i) {
704 userCSSList[i][2] = settings.charAt(i + 5) != '0';
709 this.utfEnabled = this.utfPreferred;
712 VT100.prototype.storeUserSettings = function() {
713 var settings = 'shellInABox=' + this.signature + ':' +
714 (this.utfEnabled ? '1' : '0') +
715 (this.visualBell ? '1' : '0') +
716 (this.autoprint ? '1' : '0') +
717 (this.softKeyboard ? '1' : '0') +
718 (this.blinkingCursor ? '1' : '0');
719 if (typeof userCSSList != 'undefined') {
720 for (var i = 0; i < userCSSList.length; ++i) {
721 settings += userCSSList[i][2] ? '1' : '0';
725 d.setDate(d.getDate() + 3653);
726 document.cookie = settings + ';expires=' + d.toGMTString();
729 VT100.prototype.initializeUserCSSStyles = function() {
730 this.usercssActions = [];
731 if (typeof userCSSList != 'undefined') {
734 var wasSingleSel = 1;
735 var beginOfGroup = 0;
736 for (var i = 0; i <= userCSSList.length; ++i) {
737 if (i < userCSSList.length) {
738 var label = userCSSList[i][0];
739 var newGroup = userCSSList[i][1];
740 var enabled = userCSSList[i][2];
742 // Add user style sheet to document
743 var style = document.createElement('link');
744 var id = document.createAttribute('id');
745 id.nodeValue = 'usercss-' + i;
746 style.setAttributeNode(id);
747 var rel = document.createAttribute('rel');
748 rel.nodeValue = 'stylesheet';
749 style.setAttributeNode(rel);
750 var href = document.createAttribute('href');
751 href.nodeValue = 'usercss-' + i + '.css';
752 style.setAttributeNode(href);
753 var type = document.createAttribute('type');
754 type.nodeValue = 'text/css';
755 style.setAttributeNode(type);
756 document.getElementsByTagName('head')[0].appendChild(style);
757 style.disabled = !enabled;
761 if (newGroup || i == userCSSList.length) {
762 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
763 // The last group had multiple entries that are mutually exclusive;
764 // or the previous to last group did. In either case, we need to
765 // append a "<hr />" before we can add the last group to the menu.
768 wasSingleSel = i - beginOfGroup < 1;
772 for (var j = beginOfGroup; j < i; ++j) {
773 this.usercssActions[this.usercssActions.length] =
774 function(vt100, current, begin, count) {
776 // Deselect all other entries in the group, then either select
777 // (for multiple entries in group) or toggle (for on/off entry)
778 // the current entry.
780 var entry = vt100.getChildById(vt100.menu,
784 for (var c = count; c > 0; ++j) {
785 if (entry.tagName == 'LI') {
788 var label = vt100.usercss.childNodes[j];
790 // Restore label to just the text content
791 if (typeof label.textContent == 'undefined') {
792 var s = label.innerText;
793 label.innerHTML = '';
794 label.appendChild(document.createTextNode(s));
796 label.textContent= label.textContent;
799 // User style sheets are numbered sequentially
800 var sheet = document.getElementById(
804 sheet.disabled = !sheet.disabled;
806 sheet.disabled = false;
808 if (!sheet.disabled) {
809 label.innerHTML= '<img src="/webshell/enabled.gif" />' +
813 sheet.disabled = true;
815 userCSSList[i][2] = !sheet.disabled;
818 entry = entry.nextSibling;
821 // If the font size changed, adjust cursor and line dimensions
822 this.cursor.style.cssText= '';
823 this.cursorWidth = this.cursor.clientWidth;
824 this.cursorHeight = this.lineheight.clientHeight;
825 for (i = 0; i < this.console.length; ++i) {
826 for (var line = this.console[i].firstChild; line;
827 line = line.nextSibling) {
828 line.style.height = this.cursorHeight + 'px';
833 }(this, j, beginOfGroup, i - beginOfGroup);
836 if (i == userCSSList.length) {
842 // Collect all entries in a group, before attaching them to the menu.
843 // This is necessary as we don't know whether this is a group of
844 // mutually exclusive options (which should be separated by "<hr />" on
845 // both ends), or whether this is a on/off toggle, which can be grouped
846 // together with other on/off options.
848 '<li>' + (enabled ? '<img src="/webshell/enabled.gif" />' : '') +
852 this.usercss.innerHTML = menu;
856 VT100.prototype.resetLastSelectedKey = function(e) {
857 var key = this.lastSelectedKey;
862 var position = this.mousePosition(e);
864 // We don't get all the necessary events to reliably reselect a key
865 // if we moved away from it and then back onto it. We approximate the
866 // behavior by remembering the key until either we release the mouse
867 // button (we might never get this event if the mouse has since left
868 // the window), or until we move away too far.
869 var box = this.keyboard.firstChild;
870 if (position[0] < box.offsetLeft + key.offsetWidth ||
871 position[1] < box.offsetTop + key.offsetHeight ||
872 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
873 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
874 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
875 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
876 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
877 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
878 if (this.lastSelectedKey.className) log.console('reset: deselecting');
879 this.lastSelectedKey.className = '';
880 this.lastSelectedKey = undefined;
885 VT100.prototype.showShiftState = function(state) {
886 var style = document.getElementById('shift_state');
888 this.setTextContentRaw(style,
889 '#vt100 #keyboard .shifted {' +
890 'display: inline }' +
891 '#vt100 #keyboard .unshifted {' +
894 this.setTextContentRaw(style, '');
896 var elems = this.keyboard.getElementsByTagName('I');
897 for (var i = 0; i < elems.length; ++i) {
898 if (elems[i].id == '16') {
899 elems[i].className = state ? 'selected' : '';
904 VT100.prototype.showCtrlState = function(state) {
905 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
907 ctrl.className = state ? 'selected' : '';
911 VT100.prototype.showAltState = function(state) {
912 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
914 alt.className = state ? 'selected' : '';
918 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
923 fake.shiftKey = shift;
926 return this.handleKey(fake);
929 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
930 if (elem == undefined) {
933 if (ch == '\u00A0') {
934 // should be treated as a regular space character.
937 if (ch != undefined && CH == undefined) {
938 // For letter keys, we automatically compute the uppercase character code
939 // from the lowercase one.
940 CH = ch.toUpperCase();
942 if (KEY == undefined && key != undefined) {
943 // Most keys have identically key codes for both lowercase and uppercase
944 // keypresses. Normally, only function keys would have distinct key codes,
945 // whereas regular keys have character codes.
947 } else if (KEY == undefined && CH != undefined) {
948 // For regular keys, copy the character code to the key code.
949 KEY = CH.charCodeAt(0);
951 if (key == undefined && ch != undefined) {
952 // For regular keys, copy the character code to the key code.
953 key = ch.charCodeAt(0);
955 // Convert characters to numeric character codes. If the character code
956 // is undefined (i.e. this is a function key), set it to zero.
957 ch = ch ? ch.charCodeAt(0) : 0;
958 CH = CH ? CH.charCodeAt(0) : 0;
960 // Mouse down events high light the key. We also set lastSelectedKey. This
961 // is needed to that mouseout/mouseover can keep track of the key that
962 // is currently being clicked.
963 this.addListener(elem, 'mousedown',
964 function(vt100, elem, key) { return function(e) {
965 if ((e.which || e.button) == 1) {
966 if (vt100.lastSelectedKey) {
967 vt100.lastSelectedKey.className= '';
969 // Highlight the key while the mouse button is held down.
970 if (key == 16 /* Shift */) {
971 if (!elem.className != vt100.isShift) {
972 vt100.showShiftState(!vt100.isShift);
974 } else if (key == 17 /* Ctrl */) {
975 if (!elem.className != vt100.isCtrl) {
976 vt100.showCtrlState(!vt100.isCtrl);
978 } else if (key == 18 /* Alt */) {
979 if (!elem.className != vt100.isAlt) {
980 vt100.showAltState(!vt100.isAlt);
983 elem.className = 'selected';
985 vt100.lastSelectedKey = elem;
987 return false; }; }(this, elem, key));
989 // Modifier keys update the state of the keyboard, but do not generate
990 // any key clicks that get forwarded to the application.
991 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
992 function(vt100, elem) { return function(e) {
993 if (elem == vt100.lastSelectedKey) {
994 if (key == 16 /* Shift */) {
995 // The user clicked the Shift key
996 vt100.isShift = !vt100.isShift;
997 vt100.showShiftState(vt100.isShift);
998 } else if (key == 17 /* Ctrl */) {
999 vt100.isCtrl = !vt100.isCtrl;
1000 vt100.showCtrlState(vt100.isCtrl);
1001 } else if (key == 18 /* Alt */) {
1002 vt100.isAlt = !vt100.isAlt;
1003 vt100.showAltState(vt100.isAlt);
1005 vt100.lastSelectedKey = undefined;
1007 if (vt100.lastSelectedKey) {
1008 vt100.lastSelectedKey.className = '';
1009 vt100.lastSelectedKey = undefined;
1011 return false; }; }(this, elem) :
1012 // Regular keys generate key clicks, when the mouse button is released or
1013 // when a mouse click event is received.
1014 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
1015 if (vt100.lastSelectedKey) {
1016 if (elem == vt100.lastSelectedKey) {
1017 // The user clicked a key.
1018 if (vt100.isShift) {
1019 vt100.clickedKeyboard(e, elem, CH, KEY,
1020 true, vt100.isCtrl, vt100.isAlt);
1022 vt100.clickedKeyboard(e, elem, ch, key,
1023 false, vt100.isCtrl, vt100.isAlt);
1025 vt100.isShift = false;
1026 vt100.showShiftState(false);
1027 vt100.isCtrl = false;
1028 vt100.showCtrlState(false);
1029 vt100.isAlt = false;
1030 vt100.showAltState(false);
1032 vt100.lastSelectedKey.className = '';
1033 vt100.lastSelectedKey = undefined;
1035 elem.className = '';
1036 return false; }; }(this, elem, ch, key, CH, KEY);
1037 this.addListener(elem, 'mouseup', clicked);
1038 this.addListener(elem, 'click', clicked);
1040 // When moving the mouse away from a key, check if any keys need to be
1042 this.addListener(elem, 'mouseout',
1043 function(vt100, elem, key) { return function(e) {
1044 if (key == 16 /* Shift */) {
1045 if (!elem.className == vt100.isShift) {
1046 vt100.showShiftState(vt100.isShift);
1048 } else if (key == 17 /* Ctrl */) {
1049 if (!elem.className == vt100.isCtrl) {
1050 vt100.showCtrlState(vt100.isCtrl);
1052 } else if (key == 18 /* Alt */) {
1053 if (!elem.className == vt100.isAlt) {
1054 vt100.showAltState(vt100.isAlt);
1056 } else if (elem.className) {
1057 elem.className = '';
1058 vt100.lastSelectedKey = elem;
1059 } else if (vt100.lastSelectedKey) {
1060 vt100.resetLastSelectedKey(e);
1062 return false; }; }(this, elem, key));
1064 // When moving the mouse over a key, select it if the user is still holding
1065 // the mouse button down (i.e. elem == lastSelectedKey)
1066 this.addListener(elem, 'mouseover',
1067 function(vt100, elem, key) { return function(e) {
1068 if (elem == vt100.lastSelectedKey) {
1069 if (key == 16 /* Shift */) {
1070 if (!elem.className != vt100.isShift) {
1071 vt100.showShiftState(!vt100.isShift);
1073 } else if (key == 17 /* Ctrl */) {
1074 if (!elem.className != vt100.isCtrl) {
1075 vt100.showCtrlState(!vt100.isCtrl);
1077 } else if (key == 18 /* Alt */) {
1078 if (!elem.className != vt100.isAlt) {
1079 vt100.showAltState(!vt100.isAlt);
1081 } else if (!elem.className) {
1082 elem.className = 'selected';
1085 vt100.resetLastSelectedKey(e);
1087 return false; }; }(this, elem, key));
1090 VT100.prototype.initializeKeyBindings = function(elem) {
1092 if (elem.nodeName == "I" || elem.nodeName == "B") {
1094 // Function keys. The Javascript keycode is part of the "id"
1095 var i = parseInt(elem.id);
1097 // If the id does not parse as a number, it is not a keycode.
1098 this.addKeyBinding(elem, undefined, i);
1101 var child = elem.firstChild;
1103 if (child.nodeName == "#text") {
1104 // If the key only has a text node as a child, then it is a letter.
1105 // Automatically compute the lower and upper case version of the
1107 var text = this.getTextContent(child) ||
1108 this.getTextContent(elem);
1109 this.addKeyBinding(elem, text.toLowerCase());
1110 } else if (child.nextSibling) {
1111 // If the key has two children, they are the lower and upper case
1112 // character code, respectively.
1113 this.addKeyBinding(elem, this.getTextContent(child), undefined,
1114 this.getTextContent(child.nextSibling));
1120 // Recursively parse all other child nodes.
1121 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
1122 this.initializeKeyBindings(elem);
1126 VT100.prototype.initializeKeyboardButton = function() {
1127 // Configure mouse event handlers for button that displays/hides keyboard
1128 this.addListener(this.keyboardImage, 'click',
1129 function(vt100) { return function(e) {
1130 if (vt100.keyboard.style.display != '') {
1131 if (vt100.reconnectBtn.style.visibility != '') {
1132 vt100.initializeKeyboard();
1133 vt100.showSoftKeyboard();
1136 vt100.hideSoftKeyboard();
1137 vt100.input.focus();
1139 return false; }; }(this));
1141 // Enable button that displays keyboard
1142 if (this.softKeyboard) {
1143 this.keyboardImage.style.visibility = 'visible';
1147 VT100.prototype.initializeKeyboard = function() {
1148 // Only need to initialize the keyboard the very first time. When doing so,
1149 // copy the keyboard layout from the iframe.
1150 if (this.keyboard.firstChild) {
1153 this.keyboard.innerHTML =
1154 this.layout.contentDocument.body.innerHTML;
1155 var box = this.keyboard.firstChild;
1156 this.hideSoftKeyboard();
1158 // Configure mouse event handlers for on-screen keyboard
1159 this.addListener(this.keyboard, 'click',
1160 function(vt100) { return function(e) {
1161 vt100.hideSoftKeyboard();
1162 vt100.input.focus();
1163 return false; }; }(this));
1164 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
1165 this.addListener(box, 'click', this.cancelEvent);
1166 this.addListener(box, 'mouseup',
1167 function(vt100) { return function(e) {
1168 if (vt100.lastSelectedKey) {
1169 vt100.lastSelectedKey.className = '';
1170 vt100.lastSelectedKey = undefined;
1172 return false; }; }(this));
1173 this.addListener(box, 'mouseout',
1174 function(vt100) { return function(e) {
1175 return vt100.resetLastSelectedKey(e); }; }(this));
1176 this.addListener(box, 'mouseover',
1177 function(vt100) { return function(e) {
1178 return vt100.resetLastSelectedKey(e); }; }(this));
1180 // Configure SHIFT key behavior
1181 var style = document.createElement('style');
1182 var id = document.createAttribute('id');
1183 id.nodeValue = 'shift_state';
1184 style.setAttributeNode(id);
1185 var type = document.createAttribute('type');
1186 type.nodeValue = 'text/css';
1187 style.setAttributeNode(type);
1188 document.getElementsByTagName('head')[0].appendChild(style);
1190 // Set up key bindings
1191 this.initializeKeyBindings(box);
1194 VT100.prototype.initializeElements = function(container) {
1195 // If the necessary objects have not already been defined in the HTML
1196 // page, create them now.
1198 this.container = container;
1199 } else if (!(this.container = document.getElementById('vt100'))) {
1200 this.container = document.createElement('div');
1201 this.container.id = 'vt100';
1202 document.body.appendChild(this.container);
1205 if (!this.getChildById(this.container, 'reconnect') ||
1206 !this.getChildById(this.container, 'menu') ||
1207 !this.getChildById(this.container, 'keyboard') ||
1208 !this.getChildById(this.container, 'kbd_button') ||
1209 !this.getChildById(this.container, 'kbd_img') ||
1210 !this.getChildById(this.container, 'layout') ||
1211 !this.getChildById(this.container, 'scrollable') ||
1212 !this.getChildById(this.container, 'console') ||
1213 !this.getChildById(this.container, 'alt_console') ||
1214 !this.getChildById(this.container, 'ieprobe') ||
1215 !this.getChildById(this.container, 'padding') ||
1216 !this.getChildById(this.container, 'cursor') ||
1217 !this.getChildById(this.container, 'lineheight') ||
1218 !this.getChildById(this.container, 'usercss') ||
1219 !this.getChildById(this.container, 'space') ||
1220 !this.getChildById(this.container, 'input') ||
1221 !this.getChildById(this.container, 'cliphelper')) {
1222 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
1223 // we might get a pointless warning that a suitable plugin is not yet
1224 // installed. If in doubt, we'd rather just stay silent.
1227 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
1229 embed = typeof suppressAllAudio != 'undefined' &&
1230 suppressAllAudio ? "" :
1231 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
1232 'id="beep_embed" ' +
1234 'autostart="false" ' +
1236 'enablejavascript="true" ' +
1237 'type="audio/x-wav" ' +
1240 'style="position:absolute;left:-1000px;top:-1000px" />';
1245 this.container.innerHTML =
1246 '<div id="reconnect" style="visibility: hidden">' +
1247 '<input type="button" value="Connect" ' +
1248 'onsubmit="return false" />' +
1250 '<div id="cursize" style="visibility: hidden">' +
1252 '<div id="menu"></div>' +
1253 '<div id="keyboard" unselectable="on">' +
1255 '<div id="scrollable">' +
1256 '<table id="kbd_button">' +
1257 '<tr><td width="100%"> </td>' +
1258 '<td><img id="kbd_img" src="/webshell/keyboard.png" /></td>' +
1259 '<td> </td></tr>' +
1261 '<pre id="lineheight"> </pre>' +
1262 '<pre id="console">' +
1264 '<div id="ieprobe"><span> </span></div>' +
1266 '<pre id="alt_console" style="display: none"></pre>' +
1267 '<div id="padding"></div>' +
1268 '<pre id="cursor"> </pre>' +
1270 '<div class="hidden">' +
1271 '<div id="usercss"></div>' +
1272 '<pre><div><span id="space"></span></div></pre>' +
1273 '<input type="textfield" id="input" autocorrect="off" autocapitalize="off" />' +
1274 '<input type="textfield" id="cliphelper" />' +
1275 (typeof suppressAllAudio != 'undefined' &&
1276 suppressAllAudio ? "" :
1277 embed + '<bgsound id="beep_bgsound" loop=1 />') +
1278 '<iframe id="layout" src="/webshell/keyboard.html" />' +
1282 // Find the object used for playing the "beep" sound, if any.
1283 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
1284 this.beeper = undefined;
1286 this.beeper = this.getChildById(this.container,
1288 if (!this.beeper || !this.beeper.Play) {
1289 this.beeper = this.getChildById(this.container,
1291 if (!this.beeper || typeof this.beeper.src == 'undefined') {
1292 this.beeper = undefined;
1297 // Initialize the variables for finding the text console and the
1299 this.reconnectBtn = this.getChildById(this.container,'reconnect');
1300 this.curSizeBox = this.getChildById(this.container, 'cursize');
1301 this.menu = this.getChildById(this.container, 'menu');
1302 this.keyboard = this.getChildById(this.container, 'keyboard');
1303 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
1304 this.layout = this.getChildById(this.container, 'layout');
1305 this.scrollable = this.getChildById(this.container,
1307 this.lineheight = this.getChildById(this.container,
1310 [ this.getChildById(this.container, 'console'),
1311 this.getChildById(this.container, 'alt_console') ];
1312 var ieProbe = this.getChildById(this.container, 'ieprobe');
1313 this.padding = this.getChildById(this.container, 'padding');
1314 this.cursor = this.getChildById(this.container, 'cursor');
1315 this.usercss = this.getChildById(this.container, 'usercss');
1316 this.space = this.getChildById(this.container, 'space');
1317 this.input = this.getChildById(this.container, 'input');
1318 this.cliphelper = this.getChildById(this.container,
1321 // Add any user selectable style sheets to the menu
1322 this.initializeUserCSSStyles();
1324 // Remember the dimensions of a standard character glyph. We would
1325 // expect that we could just check cursor.clientWidth/Height at any time,
1326 // but it turns out that browsers sometimes invalidate these values
1327 // (e.g. while displaying a print preview screen).
1328 this.cursorWidth = this.cursor.clientWidth;
1329 this.cursorHeight = this.lineheight.clientHeight;
1331 // IE has a slightly different boxing model, that we need to compensate for
1332 this.isIE = ieProbe.offsetTop > 1;
1333 ieProbe = undefined;
1334 this.console.innerHTML = '';
1336 // Determine if the terminal window is positioned at the beginning of the
1337 // page, or if it is embedded somewhere else in the page. For full-screen
1338 // terminals, automatically resize whenever the browser window changes.
1339 var marginTop = parseInt(this.getCurrentComputedStyle(
1340 document.body, 'marginTop'));
1341 var marginLeft = parseInt(this.getCurrentComputedStyle(
1342 document.body, 'marginLeft'));
1343 var marginRight = parseInt(this.getCurrentComputedStyle(
1344 document.body, 'marginRight'));
1345 var x = this.container.offsetLeft;
1346 var y = this.container.offsetTop;
1347 for (var parent = this.container; parent = parent.offsetParent; ) {
1348 x += parent.offsetLeft;
1349 y += parent.offsetTop;
1351 this.isEmbedded = marginTop != y ||
1353 (window.innerWidth ||
1354 document.documentElement.clientWidth ||
1355 document.body.clientWidth) -
1356 marginRight != x + this.container.offsetWidth;
1357 if (!this.isEmbedded) {
1358 // Some browsers generate resize events when the terminal is first
1359 // shown. Disable showing the size indicator until a little bit after
1360 // the terminal has been rendered the first time.
1361 this.indicateSize = false;
1362 setTimeout(function(vt100) {
1364 vt100.indicateSize = true;
1367 this.addListener(window, 'resize',
1370 vt100.hideContextMenu();
1372 vt100.showCurrentSize();
1376 // Hide extra scrollbars attached to window
1377 document.body.style.margin = '0px';
1378 try { document.body.style.overflow ='hidden'; } catch (e) { }
1379 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1382 // Set up onscreen soft keyboard
1383 this.initializeKeyboardButton();
1385 // Hide context menu
1386 this.hideContextMenu();
1388 // Add listener to reconnect button
1389 this.addListener(this.reconnectBtn.firstChild, 'click',
1392 var rc = vt100.reconnect();
1393 vt100.input.focus();
1398 // Add input listeners
1399 this.addListener(this.input, 'blur',
1401 return function() { vt100.blurCursor(); } }(this));
1402 this.addListener(this.input, 'focus',
1404 return function() { vt100.focusCursor(); } }(this));
1405 this.addListener(this.input, 'keydown',
1407 return function(e) {
1408 if (!e) e = window.event;
1409 return vt100.keyDown(e); } }(this));
1410 this.addListener(this.input, 'keypress',
1412 return function(e) {
1413 if (!e) e = window.event;
1414 return vt100.keyPressed(e); } }(this));
1415 this.addListener(this.input, 'keyup',
1417 return function(e) {
1418 if (!e) e = window.event;
1419 return vt100.keyUp(e); } }(this));
1421 // Attach listeners that move the focus to the <input> field. This way we
1422 // can make sure that we can receive keyboard input.
1423 var mouseEvent = function(vt100, type) {
1424 return function(e) {
1425 if (!e) e = window.event;
1426 return vt100.mouseEvent(e, type);
1429 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1430 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
1431 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
1433 // Check that browser supports drag and drop
1434 if ('draggable' in document.createElement('span')) {
1435 var dropEvent = function (vt100) {
1436 return function(e) {
1437 if (!e) e = window.event;
1438 if (e.preventDefault) e.preventDefault();
1439 vt100.keysPressed(e.dataTransfer.getData('Text'));
1443 // Tell the browser that we *can* drop on this target
1444 this.addListener(this.scrollable, 'dragover', cancel);
1445 this.addListener(this.scrollable, 'dragenter', cancel);
1447 // Add a listener for the drop event
1448 this.addListener(this.scrollable, 'drop', dropEvent(this));
1451 // Initialize the blank terminal window.
1452 this.currentScreen = 0;
1455 this.numScrollbackLines = 0;
1457 this.bottom = 0x7FFFFFFF;
1464 function cancel(event) {
1465 if (event.preventDefault) {
1466 event.preventDefault();
1471 VT100.prototype.getChildById = function(parent, id) {
1472 var nodeList = parent.all || parent.getElementsByTagName('*');
1473 if (typeof nodeList.namedItem == 'undefined') {
1474 for (var i = 0; i < nodeList.length; i++) {
1475 if (nodeList[i].id == id) {
1481 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1482 return elem ? elem[0] || elem : null;
1486 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1487 if (typeof elem.currentStyle != 'undefined') {
1488 return elem.currentStyle[style];
1490 return document.defaultView.getComputedStyle(elem, null)[style];
1494 VT100.prototype.reconnect = function() {
1498 VT100.prototype.showReconnect = function(state) {
1500 this.hideSoftKeyboard();
1501 this.reconnectBtn.style.visibility = '';
1503 this.reconnectBtn.style.visibility = 'hidden';
1507 VT100.prototype.repairElements = function(console) {
1508 for (var line = console.firstChild; line; line = line.nextSibling) {
1509 if (!line.clientHeight) {
1510 var newLine = document.createElement(line.tagName);
1511 newLine.style.cssText = line.style.cssText;
1512 newLine.className = line.className;
1513 if (line.tagName == 'DIV') {
1514 for (var span = line.firstChild; span; span = span.nextSibling) {
1515 var newSpan = document.createElement(span.tagName);
1516 newSpan.style.cssText = span.style.cssText;
1517 newSpan.className = span.className;
1518 this.setTextContent(newSpan, this.getTextContent(span));
1519 newLine.appendChild(newSpan);
1522 this.setTextContent(newLine, this.getTextContent(line));
1524 line.parentNode.replaceChild(newLine, line);
1530 VT100.prototype.resized = function(w, h) {
1533 VT100.prototype.resizer = function() {
1534 // Hide onscreen soft keyboard
1535 this.hideSoftKeyboard();
1537 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1538 // Recreating it, will repair it.
1539 var newCursor = document.createElement('pre');
1540 this.setTextContent(newCursor, ' ');
1541 newCursor.id = 'cursor';
1542 newCursor.style.cssText = this.cursor.style.cssText;
1543 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1544 if (!newCursor.clientHeight) {
1545 // Things are broken right now. This is probably because we are
1546 // displaying the print-preview. Just don't change any of our settings
1547 // until the print dialog is closed again.
1548 newCursor.parentNode.removeChild(newCursor);
1551 // Swap the old broken cursor for the newly created one.
1552 this.cursor.parentNode.removeChild(this.cursor);
1553 this.cursor = newCursor;
1556 // Really horrible things happen if the contents of the terminal changes
1557 // while the print-preview is showing. We get HTML elements that show up
1558 // in the DOM, but that do not take up any space. Find these elements and
1560 this.repairElements(this.console[0]);
1561 this.repairElements(this.console[1]);
1563 // Lock the cursor size to the size of a normal character. This helps with
1564 // characters that are taller/shorter than normal. Unfortunately, we will
1565 // still get confused if somebody enters a character that is wider/narrower
1566 // than normal. This can happen if the browser tries to substitute a
1567 // characters from a different font.
1568 this.cursor.style.width = this.cursorWidth + 'px';
1569 this.cursor.style.height = this.cursorHeight + 'px';
1571 // Adjust height for one pixel padding of the #vt100 element.
1572 // The latter is necessary to properly display the inactive cursor.
1573 var console = this.console[this.currentScreen];
1574 var height = (this.isEmbedded ? this.container.clientHeight
1575 : (window.innerHeight ||
1576 document.documentElement.clientHeight ||
1577 document.body.clientHeight))-1;
1578 var partial = height % this.cursorHeight;
1579 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1580 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1581 var oldTerminalHeight = this.terminalHeight;
1583 this.updateHeight();
1585 // Clip the cursor to the visible screen.
1586 var cx = this.cursorX;
1587 var cy = this.cursorY + this.numScrollbackLines;
1589 // The alternate screen never keeps a scroll back buffer.
1590 this.updateNumScrollbackLines();
1591 while (this.currentScreen && this.numScrollbackLines > 0) {
1592 console.removeChild(console.firstChild);
1593 this.numScrollbackLines--;
1595 cy -= this.numScrollbackLines;
1598 } else if (cx > this.terminalWidth) {
1599 cx = this.terminalWidth - 1;
1606 } else if (cy > this.terminalHeight) {
1607 cy = this.terminalHeight - 1;
1613 // Clip the scroll region to the visible screen.
1614 if (this.bottom > this.terminalHeight ||
1615 this.bottom == oldTerminalHeight) {
1616 this.bottom = this.terminalHeight;
1618 if (this.top >= this.bottom) {
1619 this.top = this.bottom-1;
1625 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1626 // particularly important after changing the screen number), and reset
1627 // the scroll region to the default.
1628 this.truncateLines(this.terminalWidth);
1629 this.putString(cx, cy, '', undefined);
1630 this.scrollable.scrollTop = this.numScrollbackLines *
1631 this.cursorHeight + 1;
1633 // Update classNames for lines in the scrollback buffer
1634 var line = console.firstChild;
1635 for (var i = 0; i < this.numScrollbackLines; i++) {
1636 line.className = 'scrollback';
1637 line = line.nextSibling;
1640 line.className = '';
1641 line = line.nextSibling;
1644 // Reposition the reconnect button
1645 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1647 this.reconnectBtn.clientWidth)/2 + 'px';
1648 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1649 this.reconnectBtn.clientHeight)/2 + 'px';
1651 // Send notification that the window size has been changed
1652 this.resized(this.terminalWidth, this.terminalHeight);
1655 VT100.prototype.showCurrentSize = function() {
1656 if (!this.indicateSize) {
1659 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1660 this.terminalHeight;
1661 this.curSizeBox.style.left =
1662 (this.terminalWidth*this.cursorWidth/
1664 this.curSizeBox.clientWidth)/2 + 'px';
1665 this.curSizeBox.style.top =
1666 (this.terminalHeight*this.cursorHeight -
1667 this.curSizeBox.clientHeight)/2 + 'px';
1668 this.curSizeBox.style.visibility = '';
1669 if (this.curSizeTimeout) {
1670 clearTimeout(this.curSizeTimeout);
1673 // Only show the terminal size for a short amount of time after resizing.
1674 // Then hide this information, again. Some browsers generate resize events
1675 // throughout the entire resize operation. This is nice, and we will show
1676 // the terminal size while the user is dragging the window borders.
1677 // Other browsers only generate a single event when the user releases the
1678 // mouse. In those cases, we can only show the terminal size once at the
1679 // end of the resize operation.
1680 this.curSizeTimeout = setTimeout(function(vt100) {
1682 vt100.curSizeTimeout = null;
1683 vt100.curSizeBox.style.visibility = 'hidden';
1688 VT100.prototype.selection = function() {
1690 return '' + (window.getSelection && window.getSelection() ||
1691 document.selection && document.selection.type == 'Text' &&
1692 document.selection.createRange().text || '');
1698 VT100.prototype.cancelEvent = function(event) {
1700 // For non-IE browsers
1701 event.stopPropagation();
1702 event.preventDefault();
1707 event.cancelBubble = true;
1708 event.returnValue = false;
1716 VT100.prototype.mousePosition = function(event) {
1717 var offsetX = this.container.offsetLeft;
1718 var offsetY = this.container.offsetTop;
1719 for (var e = this.container; e = e.offsetParent; ) {
1720 offsetX += e.offsetLeft;
1721 offsetY += e.offsetTop;
1723 return [ event.clientX - offsetX,
1724 event.clientY - offsetY ];
1727 VT100.prototype.mouseEvent = function(event, type) {
1728 // If any text is currently selected, do not move the focus as that would
1729 // invalidate the selection.
1730 var selection = this.selection();
1731 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1735 // Compute mouse position in characters.
1736 var position = this.mousePosition(event);
1737 var x = Math.floor(position[0] / this.cursorWidth);
1738 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1739 this.cursorHeight) - this.numScrollbackLines;
1741 if (x >= this.terminalWidth) {
1742 x = this.terminalWidth - 1;
1749 if (y >= this.terminalHeight) {
1750 y = this.terminalHeight - 1;
1758 // Compute button number and modifier keys.
1759 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
1760 typeof event.pageX != 'undefined' ? event.button :
1761 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1762 if (button != undefined) {
1763 if (event.shiftKey) {
1766 if (event.altKey || event.metaKey) {
1769 if (event.ctrlKey) {
1774 // Report mouse events if they happen inside of the current screen and
1775 // with the SHIFT key unpressed. Both of these restrictions do not apply
1776 // for button releases, as we always want to report those.
1777 if (this.mouseReporting && !selection.length &&
1778 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1779 if (inside || type != 0 /* MOUSE_DOWN */) {
1780 if (button != undefined) {
1781 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1782 String.fromCharCode(x + 33) +
1783 String.fromCharCode(y + 33);
1784 if (type != 2 /* MOUSE_CLICK */) {
1785 this.keysPressed(report);
1788 // If we reported the event, stop propagating it (not sure, if this
1789 // actually works on most browsers; blocking the global "oncontextmenu"
1790 // even is still necessary).
1791 return this.cancelEvent(event);
1796 // Bring up context menu.
1797 if (button == 2 && !event.shiftKey) {
1798 if (type == 0 /* MOUSE_DOWN */) {
1799 this.showContextMenu(position[0], position[1]);
1801 return this.cancelEvent(event);
1804 if (this.mouseReporting) {
1806 event.shiftKey = false;
1814 VT100.prototype.replaceChar = function(s, ch, repl) {
1815 for (var i = -1;;) {
1816 i = s.indexOf(ch, i + 1);
1820 s = s.substr(0, i) + repl + s.substr(i + 1);
1825 VT100.prototype.htmlEscape = function(s) {
1826 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1827 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1830 VT100.prototype.getTextContent = function(elem) {
1831 return elem.textContent ||
1832 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1835 VT100.prototype.setTextContentRaw = function(elem, s) {
1836 // Updating the content of an element is an expensive operation. It actually
1837 // pays off to first check whether the element is still unchanged.
1838 if (typeof elem.textContent == 'undefined') {
1839 if (elem.innerText != s) {
1843 // Very old versions of IE do not allow setting innerText. Instead,
1844 // remove all children, by setting innerHTML and then set the text
1845 // using DOM methods.
1846 elem.innerHTML = '';
1847 elem.appendChild(document.createTextNode(
1848 this.replaceChar(s, ' ', '\u00A0')));
1852 if (elem.textContent != s) {
1853 elem.textContent = s;
1858 VT100.prototype.setTextContent = function(elem, s) {
1859 // Check if we find any URLs in the text. If so, automatically convert them
1861 if (this.urlRE && this.urlRE.test(s)) {
1865 if (RegExp.leftContext != null) {
1866 inner += this.htmlEscape(RegExp.leftContext);
1867 consumed += RegExp.leftContext.length;
1869 var url = this.htmlEscape(RegExp.lastMatch);
1872 // If no protocol was specified, try to guess a reasonable one.
1873 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1874 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1875 var slash = url.indexOf('/');
1876 var at = url.indexOf('@');
1877 var question = url.indexOf('?');
1879 (at < question || question < 0) &&
1880 (slash < 0 || (question > 0 && slash > question))) {
1881 fullUrl = 'mailto:' + url;
1883 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1888 inner += '<a target="vt100Link" href="' + fullUrl +
1889 '">' + url + '</a>';
1890 consumed += RegExp.lastMatch.length;
1891 s = s.substr(consumed);
1892 if (!this.urlRE.test(s)) {
1893 if (RegExp.rightContext != null) {
1894 inner += this.htmlEscape(RegExp.rightContext);
1899 elem.innerHTML = inner;
1903 this.setTextContentRaw(elem, s);
1906 VT100.prototype.insertBlankLine = function(y, color, style) {
1907 // Insert a blank line a position y. This method ignores the scrollback
1908 // buffer. The caller has to add the length of the scrollback buffer to
1909 // the position, if necessary.
1910 // If the position is larger than the number of current lines, this
1911 // method just adds a new line right after the last existing one. It does
1912 // not add any missing lines in between. It is the caller's responsibility
1915 color = 'ansi0 bgAnsi15';
1921 if (color != 'ansi0 bgAnsi15' && !style) {
1922 line = document.createElement('pre');
1923 this.setTextContent(line, '\n');
1925 line = document.createElement('div');
1926 var span = document.createElement('span');
1927 span.style.cssText = style;
1928 span.className = color;
1929 this.setTextContent(span, this.spaces(this.terminalWidth));
1930 line.appendChild(span);
1932 line.style.height = this.cursorHeight + 'px';
1933 var console = this.console[this.currentScreen];
1934 if (console.childNodes.length > y) {
1935 console.insertBefore(line, console.childNodes[y]);
1937 console.appendChild(line);
1941 VT100.prototype.updateWidth = function() {
1942 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1943 this.cursorWidth*this.scale);
1944 return this.terminalWidth;
1947 VT100.prototype.updateHeight = function() {
1948 // We want to be able to display either a terminal window that fills the
1949 // entire browser window, or a terminal window that is contained in a
1950 // <div> which is embededded somewhere in the web page.
1951 if (this.isEmbedded) {
1952 // Embedded terminal. Use size of the containing <div> (id="vt100").
1953 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1956 // Use the full browser window.
1957 this.terminalHeight = Math.floor(((window.innerHeight ||
1958 document.documentElement.clientHeight ||
1959 document.body.clientHeight)-1)/
1962 return this.terminalHeight;
1965 VT100.prototype.updateNumScrollbackLines = function() {
1966 var scrollback = Math.floor(
1967 this.console[this.currentScreen].offsetHeight /
1968 this.cursorHeight) -
1969 this.terminalHeight;
1970 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1971 return this.numScrollbackLines;
1974 VT100.prototype.truncateLines = function(width) {
1978 for (var line = this.console[this.currentScreen].firstChild; line;
1979 line = line.nextSibling) {
1980 if (line.tagName == 'DIV') {
1983 // Traverse current line and truncate it once we saw "width" characters
1984 for (var span = line.firstChild; span;
1985 span = span.nextSibling) {
1986 var s = this.getTextContent(span);
1988 if (x + l > width) {
1989 this.setTextContent(span, s.substr(0, width - x));
1990 while (span.nextSibling) {
1991 line.removeChild(line.lastChild);
1997 // Prune white space from the end of the current line
1998 var span = line.lastChild;
2000 span.className == 'ansi0 bgAnsi15' &&
2001 !span.style.cssText.length) {
2002 // Scan backwards looking for first non-space character
2003 var s = this.getTextContent(span);
2004 for (var i = s.length; i--; ) {
2005 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2006 if (i+1 != s.length) {
2007 this.setTextContent(s.substr(0, i+1));
2015 span = span.previousSibling;
2017 // Remove blank <span>'s from end of line
2018 line.removeChild(sibling);
2020 // Remove entire line (i.e. <div>), if empty
2021 var blank = document.createElement('pre');
2022 blank.style.height = this.cursorHeight + 'px';
2023 this.setTextContent(blank, '\n');
2024 line.parentNode.replaceChild(blank, line);
2032 VT100.prototype.putString = function(x, y, text, color, style) {
2034 color = 'ansi0 bgAnsi15';
2039 var yIdx = y + this.numScrollbackLines;
2045 var console = this.console[this.currentScreen];
2046 if (!text.length && (yIdx >= console.childNodes.length ||
2047 console.childNodes[yIdx].tagName != 'DIV')) {
2048 // Positioning cursor to a blank location
2051 // Create missing blank lines at end of page
2052 while (console.childNodes.length <= yIdx) {
2053 // In order to simplify lookups, we want to make sure that each line
2054 // is represented by exactly one element (and possibly a whole bunch of
2056 // For non-blank lines, we can create a <div> containing one or more
2057 // <span>s. For blank lines, this fails as browsers tend to optimize them
2058 // away. But fortunately, a <pre> tag containing a newline character
2059 // appears to work for all browsers (a would also work, but then
2060 // copying from the browser window would insert superfluous spaces into
2062 this.insertBlankLine(yIdx);
2064 line = console.childNodes[yIdx];
2066 // If necessary, promote blank '\n' line to a <div> tag
2067 if (line.tagName != 'DIV') {
2068 var div = document.createElement('div');
2069 div.style.height = this.cursorHeight + 'px';
2070 div.innerHTML = '<span></span>';
2071 console.replaceChild(div, line);
2075 // Scan through list of <span>'s until we find the one where our text
2077 span = line.firstChild;
2079 while (span.nextSibling && xPos < x) {
2080 len = this.getTextContent(span).length;
2081 if (xPos + len > x) {
2085 span = span.nextSibling;
2089 // If current <span> is not long enough, pad with spaces or add new
2091 s = this.getTextContent(span);
2092 var oldColor = span.className;
2093 var oldStyle = span.style.cssText;
2094 if (xPos + s.length < x) {
2095 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
2096 span = document.createElement('span');
2097 line.appendChild(span);
2098 span.className = 'ansi0 bgAnsi15';
2099 span.style.cssText = '';
2100 oldColor = 'ansi0 bgAnsi15';
2107 } while (xPos + s.length < x);
2110 // If styles do not match, create a new <span>
2111 var del = text.length - s.length + x - xPos;
2112 if (oldColor != color ||
2113 (oldStyle != style && (oldStyle || style))) {
2115 // Replacing text at beginning of existing <span>
2116 if (text.length >= s.length) {
2117 // New text is equal or longer than existing text
2120 // Insert new <span> before the current one, then remove leading
2121 // part of existing <span>, adjust style of new <span>, and finally
2123 sibling = document.createElement('span');
2124 line.insertBefore(sibling, span);
2125 this.setTextContent(span, s.substr(text.length));
2130 // Replacing text some way into the existing <span>
2131 var remainder = s.substr(x + text.length - xPos);
2132 this.setTextContent(span, s.substr(0, x - xPos));
2134 sibling = document.createElement('span');
2135 if (span.nextSibling) {
2136 line.insertBefore(sibling, span.nextSibling);
2138 if (remainder.length) {
2139 sibling = document.createElement('span');
2140 sibling.className = oldColor;
2141 sibling.style.cssText = oldStyle;
2142 this.setTextContent(sibling, remainder);
2143 line.insertBefore(sibling, span.nextSibling);
2146 line.appendChild(sibling);
2148 if (remainder.length) {
2149 sibling = document.createElement('span');
2150 sibling.className = oldColor;
2151 sibling.style.cssText = oldStyle;
2152 this.setTextContent(sibling, remainder);
2153 line.appendChild(sibling);
2158 span.className = color;
2159 span.style.cssText = style;
2161 // Overwrite (partial) <span> with new text
2162 s = s.substr(0, x - xPos) +
2164 s.substr(x + text.length - xPos);
2166 this.setTextContent(span, s);
2169 // Delete all subsequent <span>'s that have just been overwritten
2170 sibling = span.nextSibling;
2171 while (del > 0 && sibling) {
2172 s = this.getTextContent(sibling);
2175 line.removeChild(sibling);
2177 sibling = span.nextSibling;
2179 this.setTextContent(sibling, s.substr(del));
2184 // Merge <span> with next sibling, if styles are identical
2185 if (sibling && span.className == sibling.className &&
2186 span.style.cssText == sibling.style.cssText) {
2187 this.setTextContent(span,
2188 this.getTextContent(span) +
2189 this.getTextContent(sibling));
2190 line.removeChild(sibling);
2196 this.cursorX = x + text.length;
2197 if (this.cursorX >= this.terminalWidth) {
2198 this.cursorX = this.terminalWidth - 1;
2199 if (this.cursorX < 0) {
2205 if (!this.cursor.style.visibility) {
2206 var idx = this.cursorX - xPos;
2208 // If we are in a non-empty line, take the cursor Y position from the
2209 // other elements in this line. If dealing with broken, non-proportional
2210 // fonts, this is likely to yield better results.
2211 pixelY = span.offsetTop +
2212 span.offsetParent.offsetTop;
2213 s = this.getTextContent(span);
2214 var nxtIdx = idx - s.length;
2216 this.setTextContent(this.cursor, s.charAt(idx));
2217 pixelX = span.offsetLeft +
2218 idx*span.offsetWidth / s.length;
2221 pixelX = span.offsetLeft + span.offsetWidth;
2223 if (span.nextSibling) {
2224 s = this.getTextContent(span.nextSibling);
2225 this.setTextContent(this.cursor, s.charAt(nxtIdx));
2227 pixelX = span.nextSibling.offsetLeft +
2228 nxtIdx*span.offsetWidth / s.length;
2231 this.setTextContent(this.cursor, ' ');
2235 this.setTextContent(this.cursor, ' ');
2239 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
2242 this.setTextContent(this.space, this.spaces(this.cursorX));
2243 this.cursor.style.left = (this.space.offsetWidth +
2244 console.offsetLeft)/this.scale + 'px';
2246 this.cursorY = yIdx - this.numScrollbackLines;
2248 this.cursor.style.top = pixelY + 'px';
2250 this.cursor.style.top = yIdx*this.cursorHeight +
2251 console.offsetTop + 'px';
2255 // Merge <span> with previous sibling, if styles are identical
2256 if ((sibling = span.previousSibling) &&
2257 span.className == sibling.className &&
2258 span.style.cssText == sibling.style.cssText) {
2259 this.setTextContent(span,
2260 this.getTextContent(sibling) +
2261 this.getTextContent(span));
2262 line.removeChild(sibling);
2265 // Prune white space from the end of the current line
2266 span = line.lastChild;
2268 span.className == 'ansi0 bgAnsi15' &&
2269 !span.style.cssText.length) {
2270 // Scan backwards looking for first non-space character
2271 s = this.getTextContent(span);
2272 for (var i = s.length; i--; ) {
2273 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2274 if (i+1 != s.length) {
2275 this.setTextContent(s.substr(0, i+1));
2283 span = span.previousSibling;
2285 // Remove blank <span>'s from end of line
2286 line.removeChild(sibling);
2288 // Remove entire line (i.e. <div>), if empty
2289 var blank = document.createElement('pre');
2290 blank.style.height = this.cursorHeight + 'px';
2291 this.setTextContent(blank, '\n');
2292 line.parentNode.replaceChild(blank, line);
2299 VT100.prototype.gotoXY = function(x, y) {
2300 if (x >= this.terminalWidth) {
2301 x = this.terminalWidth - 1;
2307 if (this.offsetMode) {
2312 maxY = this.terminalHeight;
2320 this.putString(x, y, '', undefined);
2321 this.needWrap = false;
2324 VT100.prototype.gotoXaY = function(x, y) {
2325 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
2328 VT100.prototype.refreshInvertedState = function() {
2329 if (this.isInverted) {
2330 this.scrollable.className += ' inverted';
2332 this.scrollable.className = this.scrollable.className.
2333 replace(/ *inverted/, '');
2337 VT100.prototype.enableAlternateScreen = function(state) {
2338 // Don't do anything, if we are already on the desired screen
2339 if ((state ? 1 : 0) == this.currentScreen) {
2340 // Calling the resizer is not actually necessary. But it is a good way
2341 // of resetting state that might have gotten corrupted.
2346 // We save the full state of the normal screen, when we switch away from it.
2347 // But for the alternate screen, no saving is necessary. We always reset
2348 // it when we switch to it.
2353 // Display new screen, and initialize state (the resizer does that for us).
2354 this.currentScreen = state ? 1 : 0;
2355 this.console[1-this.currentScreen].style.display = 'none';
2356 this.console[this.currentScreen].style.display = '';
2358 // Select appropriate character pitch.
2359 var transform = this.getTransformName();
2362 // Upon enabling the alternate screen, we switch to 80 column mode. But
2363 // upon returning to the regular screen, we restore the mode that was
2364 // in effect previously.
2365 this.console[1].style[transform] = '';
2368 this.console[this.currentScreen].style[transform];
2369 this.cursor.style[transform] = style;
2370 this.space.style[transform] = style;
2371 this.scale = style == '' ? 1.0:1.65;
2372 if (transform == 'filter') {
2373 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
2378 // If we switched to the alternate screen, reset it completely. Otherwise,
2379 // restore the saved state.
2382 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2384 this.restoreCursor();
2388 VT100.prototype.hideCursor = function() {
2389 var hidden = this.cursor.style.visibility == 'hidden';
2391 this.cursor.style.visibility = 'hidden';
2397 VT100.prototype.showCursor = function(x, y) {
2398 if (this.cursor.style.visibility) {
2399 this.cursor.style.visibility = '';
2400 this.putString(x == undefined ? this.cursorX : x,
2401 y == undefined ? this.cursorY : y,
2408 VT100.prototype.scrollBack = function() {
2409 var i = this.scrollable.scrollTop -
2410 this.scrollable.clientHeight;
2411 this.scrollable.scrollTop = i < 0 ? 0 : i;
2414 VT100.prototype.scrollFore = function() {
2415 var i = this.scrollable.scrollTop +
2416 this.scrollable.clientHeight;
2417 this.scrollable.scrollTop = i > this.numScrollbackLines *
2418 this.cursorHeight + 1
2419 ? this.numScrollbackLines *
2420 this.cursorHeight + 1
2424 VT100.prototype.spaces = function(i) {
2432 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2437 if (w > this.terminalWidth) {
2438 w = this.terminalWidth;
2440 if ((w -= x) <= 0) {
2447 if (h > this.terminalHeight) {
2448 h = this.terminalHeight;
2450 if ((h -= y) <= 0) {
2454 // Special case the situation where we clear the entire screen, and we do
2455 // not have a scrollback buffer. In that case, we should just remove all
2457 if (!this.numScrollbackLines &&
2458 w == this.terminalWidth && h == this.terminalHeight &&
2459 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2460 var console = this.console[this.currentScreen];
2461 while (console.lastChild) {
2462 console.removeChild(console.lastChild);
2464 this.putString(this.cursorX, this.cursorY, '', undefined);
2466 var hidden = this.hideCursor();
2467 var cx = this.cursorX;
2468 var cy = this.cursorY;
2469 var s = this.spaces(w);
2470 for (var i = y+h; i-- > y; ) {
2471 this.putString(x, i, s, color, style);
2473 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2477 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2479 var className = [ ];
2481 var console = this.console[this.currentScreen];
2482 if (sY >= console.childNodes.length) {
2483 text[0] = this.spaces(w);
2484 className[0] = undefined;
2485 style[0] = undefined;
2487 var line = console.childNodes[sY];
2488 if (line.tagName != 'DIV' || !line.childNodes.length) {
2489 text[0] = this.spaces(w);
2490 className[0] = undefined;
2491 style[0] = undefined;
2494 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2495 var s = this.getTextContent(span);
2498 var o = sX > x ? sX - x : 0;
2499 text[text.length] = s.substr(o, w);
2500 className[className.length] = span.className;
2501 style[style.length] = span.style.cssText;
2507 text[text.length] = this.spaces(w);
2508 className[className.length] = undefined;
2509 style[style.length] = undefined;
2513 var hidden = this.hideCursor();
2514 var cx = this.cursorX;
2515 var cy = this.cursorY;
2516 for (var i = 0; i < text.length; i++) {
2519 color = className[i];
2521 color = 'ansi0 bgAnsi15';
2523 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2524 dX += text[i].length;
2526 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2529 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2531 var left = incX < 0 ? -incX : 0;
2532 var right = incX > 0 ? incX : 0;
2533 var up = incY < 0 ? -incY : 0;
2534 var down = incY > 0 ? incY : 0;
2536 // Clip region against terminal size
2537 var dontScroll = null;
2542 if (w > this.terminalWidth - right) {
2543 w = this.terminalWidth - right;
2545 if ((w -= x) <= 0) {
2552 if (h > this.terminalHeight - down) {
2553 h = this.terminalHeight - down;
2559 if (style && style.indexOf('underline')) {
2560 // Different terminal emulators disagree on the attributes that
2561 // are used for scrolling. The consensus seems to be, never to
2562 // fill with underlined spaces. N.B. this is different from the
2563 // cases when the user blanks a region. User-initiated blanking
2564 // always fills with all of the current attributes.
2565 style = style.replace(/text-decoration:underline;/, '');
2568 // Compute current scroll position
2569 var scrollPos = this.numScrollbackLines -
2570 (this.scrollable.scrollTop-1) / this.cursorHeight;
2572 // Determine original cursor position. Hide cursor temporarily to avoid
2573 // visual artifacts.
2574 var hidden = this.hideCursor();
2575 var cx = this.cursorX;
2576 var cy = this.cursorY;
2577 var console = this.console[this.currentScreen];
2579 if (!incX && !x && w == this.terminalWidth) {
2580 // Scrolling entire lines
2583 if (!this.currentScreen && y == -incY &&
2584 h == this.terminalHeight + incY) {
2585 // Scrolling up with adding to the scrollback buffer. This is only
2586 // possible if there are at least as many lines in the console,
2587 // as the terminal is high
2588 while (console.childNodes.length < this.terminalHeight) {
2589 this.insertBlankLine(this.terminalHeight);
2592 // Add new lines at bottom in order to force scrolling
2593 for (var i = 0; i < y; i++) {
2594 this.insertBlankLine(console.childNodes.length, color, style);
2597 // Adjust the number of lines in the scrollback buffer by
2598 // removing excess entries.
2599 this.updateNumScrollbackLines();
2600 while (this.numScrollbackLines >
2601 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2602 console.removeChild(console.firstChild);
2603 this.numScrollbackLines--;
2606 // Mark lines in the scrollback buffer, so that they do not get
2608 for (var i = this.numScrollbackLines, j = -incY;
2609 i-- > 0 && j-- > 0; ) {
2610 console.childNodes[i].className = 'scrollback';
2613 // Scrolling up without adding to the scrollback buffer.
2616 console.childNodes.length >
2617 this.numScrollbackLines + y + incY; ) {
2618 console.removeChild(console.childNodes[
2619 this.numScrollbackLines + y + incY]);
2622 // If we used to have a scrollback buffer, then we must make sure
2623 // that we add back blank lines at the bottom of the terminal.
2624 // Similarly, if we are scrolling in the middle of the screen,
2625 // we must add blank lines to ensure that the bottom of the screen
2626 // does not move up.
2627 if (this.numScrollbackLines > 0 ||
2628 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2629 for (var i = -incY; i-- > 0; ) {
2630 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2639 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2640 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2642 for (var i = incY; i--; ) {
2643 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2647 // Scrolling partial lines
2649 // Scrolling up or horizontally within a line
2650 for (var i = y + this.numScrollbackLines;
2651 i < y + this.numScrollbackLines + h;
2653 this.copyLineSegment(x + incX, i + incY, x, i, w);
2657 for (var i = y + this.numScrollbackLines + h;
2658 i-- > y + this.numScrollbackLines; ) {
2659 this.copyLineSegment(x + incX, i + incY, x, i, w);
2663 // Clear blank regions
2665 this.clearRegion(x, y, incX, h, color, style);
2666 } else if (incX < 0) {
2667 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2670 this.clearRegion(x, y, w, incY, color, style);
2671 } else if (incY < 0) {
2672 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2676 // Reset scroll position
2677 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2678 this.cursorHeight + 1;
2680 // Move cursor back to its original position
2681 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2685 VT100.prototype.copy = function(selection) {
2686 if (selection == undefined) {
2687 selection = this.selection();
2689 this.internalClipboard = undefined;
2690 if (selection.length) {
2693 this.cliphelper.value = selection;
2694 this.cliphelper.select();
2695 this.cliphelper.createTextRange().execCommand('copy');
2697 this.internalClipboard = selection;
2699 this.cliphelper.value = '';
2703 VT100.prototype.copyLast = function() {
2704 // Opening the context menu can remove the selection. We try to prevent this
2705 // from happening, but that is not possible for all browsers. So, instead,
2706 // we compute the selection before showing the menu.
2707 this.copy(this.lastSelection);
2710 VT100.prototype.pasteFnc = function() {
2711 var clipboard = undefined;
2712 if (this.internalClipboard != undefined) {
2713 clipboard = this.internalClipboard;
2716 this.cliphelper.value = '';
2717 this.cliphelper.createTextRange().execCommand('paste');
2718 clipboard = this.cliphelper.value;
2722 this.cliphelper.value = '';
2723 if (clipboard && this.menu.style.visibility == 'hidden') {
2725 this.keysPressed('' + clipboard);
2732 VT100.prototype.pasteBrowserFnc = function() {
2733 var clipboard = prompt("Paste into this box:","");
2734 if (clipboard != undefined) {
2735 return this.keysPressed('' + clipboard);
2739 VT100.prototype.toggleUTF = function() {
2740 this.utfEnabled = !this.utfEnabled;
2742 // We always persist the last value that the user selected. Not necessarily
2743 // the last value that a random program requested.
2744 this.utfPreferred = this.utfEnabled;
2747 VT100.prototype.toggleBell = function() {
2748 this.visualBell = !this.visualBell;
2751 VT100.prototype.toggleSoftKeyboard = function() {
2752 this.softKeyboard = !this.softKeyboard;
2753 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2756 VT100.prototype.deselectKeys = function(elem) {
2757 if (elem && elem.className == 'selected') {
2758 elem.className = '';
2760 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2761 this.deselectKeys(elem);
2765 VT100.prototype.showSoftKeyboard = function() {
2766 // Make sure no key is currently selected
2767 this.lastSelectedKey = undefined;
2768 this.deselectKeys(this.keyboard);
2769 this.isShift = false;
2770 this.showShiftState(false);
2771 this.isCtrl = false;
2772 this.showCtrlState(false);
2774 this.showAltState(false);
2776 this.keyboard.style.left = '0px';
2777 this.keyboard.style.top = '0px';
2778 this.keyboard.style.width = this.container.offsetWidth + 'px';
2779 this.keyboard.style.height = this.container.offsetHeight + 'px';
2780 this.keyboard.style.visibility = 'hidden';
2781 this.keyboard.style.display = '';
2783 var kbd = this.keyboard.firstChild;
2785 var transform = this.getTransformName();
2787 kbd.style[transform] = '';
2788 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2789 scale = (kbd.offsetWidth/
2790 this.container.offsetWidth)/0.9;
2792 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2793 scale = Math.max((kbd.offsetHeight/
2794 this.container.offsetHeight)/0.9);
2796 var style = this.getTransformStyle(transform,
2797 scale > 1.0 ? scale : undefined);
2798 kbd.style[transform] = style;
2800 if (transform == 'filter') {
2803 kbd.style.left = ((this.container.offsetWidth -
2804 kbd.offsetWidth/scale)/2) + 'px';
2805 kbd.style.top = ((this.container.offsetHeight -
2806 kbd.offsetHeight/scale)/2) + 'px';
2808 this.keyboard.style.visibility = 'visible';
2811 VT100.prototype.hideSoftKeyboard = function() {
2812 this.keyboard.style.display = 'none';
2815 VT100.prototype.toggleCursorBlinking = function() {
2816 this.blinkingCursor = !this.blinkingCursor;
2819 VT100.prototype.about = function() {
2820 alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2821 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2822 "For more information check http://shellinabox.com");
2825 VT100.prototype.hideContextMenu = function() {
2826 this.menu.style.visibility = 'hidden';
2827 this.menu.style.top = '-100px';
2828 this.menu.style.left = '-100px';
2829 this.menu.style.width = '0px';
2830 this.menu.style.height = '0px';
2833 VT100.prototype.extendContextMenu = function(entries, actions) {
2836 VT100.prototype.showContextMenu = function(x, y) {
2837 this.menu.innerHTML =
2838 '<table class="popup" ' +
2839 'cellpadding="0" cellspacing="0">' +
2841 '<ul id="menuentries">' +
2842 '<li id="beginclipboard">Copy</li>' +
2843 '<li id="endclipboard">Paste</li>' +
2844 '<li id="browserclipboard">Paste from browser</li>' +
2846 '<li id="reset">Reset</li>' +
2848 '<li id="beginconfig">' +
2849 (this.utfEnabled ? '<img src="/webshell/enabled.gif" />' : '') +
2852 (this.visualBell ? '<img src="/webshell/enabled.gif" />' : '') +
2855 (this.softKeyboard ? '<img src="/webshell/enabled.gif" />' : '') +
2856 'Onscreen Keyboard</li>' +
2857 '<li id="endconfig">' +
2858 (this.blinkingCursor ? '<img src="/webshell/enabled.gif" />' : '') +
2859 'Blinking Cursor</li>'+
2860 (this.usercss.firstChild ?
2861 '<hr id="beginusercss" />' +
2862 this.usercss.innerHTML +
2863 '<hr id="endusercss" />' :
2865 '<li id="about">About...</li>' +
2870 var popup = this.menu.firstChild;
2871 var menuentries = this.getChildById(popup, 'menuentries');
2873 // Determine menu entries that should be disabled
2874 this.lastSelection = this.selection();
2875 if (!this.lastSelection.length) {
2876 menuentries.firstChild.className
2879 var p = this.pasteFnc();
2881 menuentries.childNodes[1].className
2885 // Actions for default items
2886 var actions = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2887 this.toggleUTF, this.toggleBell,
2888 this.toggleSoftKeyboard,
2889 this.toggleCursorBlinking ];
2891 // Actions for user CSS styles (if any)
2892 for (var i = 0; i < this.usercssActions.length; ++i) {
2893 actions[actions.length] = this.usercssActions[i];
2895 actions[actions.length] = this.about;
2897 // Allow subclasses to dynamically add entries to the context menu
2898 this.extendContextMenu(menuentries, actions);
2900 // Hook up event listeners
2901 for (var node = menuentries.firstChild, i = 0; node;
2902 node = node.nextSibling) {
2903 if (node.tagName == 'LI') {
2904 if (node.className != 'disabled') {
2905 this.addListener(node, 'mouseover',
2906 function(vt100, node) {
2908 node.className = 'hover';
2911 this.addListener(node, 'mouseout',
2912 function(vt100, node) {
2914 node.className = '';
2917 this.addListener(node, 'mousedown',
2918 function(vt100, action) {
2919 return function(event) {
2920 vt100.hideContextMenu();
2922 vt100.storeUserSettings();
2923 return vt100.cancelEvent(event || window.event);
2925 }(this, actions[i]));
2926 this.addListener(node, 'mouseup',
2928 return function(event) {
2929 return vt100.cancelEvent(event || window.event);
2932 this.addListener(node, 'mouseclick',
2934 return function(event) {
2935 return vt100.cancelEvent(event || window.event);
2943 // Position menu next to the mouse pointer
2944 this.menu.style.left = '0px';
2945 this.menu.style.top = '0px';
2946 this.menu.style.width = this.container.offsetWidth + 'px';
2947 this.menu.style.height = this.container.offsetHeight + 'px';
2948 popup.style.left = '0px';
2949 popup.style.top = '0px';
2952 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2953 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2958 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2959 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2964 popup.style.left = x + 'px';
2965 popup.style.top = y + 'px';
2967 // Block all other interactions with the terminal emulator
2968 this.addListener(this.menu, 'click', function(vt100) {
2970 vt100.hideContextMenu();
2975 this.menu.style.visibility = '';
2978 VT100.prototype.keysPressed = function(ch) {
2979 for (var i = 0; i < ch.length; i++) {
2980 var c = ch.charCodeAt(i);
2981 this.vt100(c >= 7 && c <= 15 ||
2982 c == 24 || c == 26 || c == 27 || c >= 32
2983 ? String.fromCharCode(c) : '<' + c + '>');
2987 VT100.prototype.applyModifiers = function(ch, event) {
2989 if (event.ctrlKey) {
2990 if (ch >= 32 && ch <= 127) {
2991 // For historic reasons, some control characters are treated specially
2993 case /* 3 */ 51: ch = 27; break;
2994 case /* 4 */ 52: ch = 28; break;
2995 case /* 5 */ 53: ch = 29; break;
2996 case /* 6 */ 54: ch = 30; break;
2997 case /* 7 */ 55: ch = 31; break;
2998 case /* 8 */ 56: ch = 127; break;
2999 case /* ? */ 63: ch = 127; break;
3000 default: ch &= 31; break;
3004 return String.fromCharCode(ch);
3010 VT100.prototype.handleKey = function(event) {
3011 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
3012 // (event.shiftKey || event.ctrlKey || event.altKey ||
3013 // event.metaKey ? ', ' +
3014 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3015 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3018 if (typeof event.charCode != 'undefined') {
3019 // non-IE keypress events have a translated charCode value. Also, our
3020 // fake events generated when receiving keydown events include this data
3022 ch = event.charCode;
3023 key = event.keyCode;
3025 // When sending a keypress event, IE includes the translated character
3026 // code in the keyCode field.
3031 // Apply modifier keys (ctrl and shift)
3035 ch = this.applyModifiers(ch, event);
3037 // By this point, "ch" is either defined and contains the character code, or
3038 // it is undefined and "key" defines the code of a function key
3039 if (ch != undefined) {
3040 this.scrollable.scrollTop = this.numScrollbackLines *
3041 this.cursorHeight + 1;
3043 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
3044 // Many programs have difficulties dealing with parametrized escape
3045 // sequences for function keys. Thus, if ALT is the only modifier
3046 // key, return Emacs-style keycodes for commonly used keys.
3048 case 33: /* Page Up */ ch = '\u001B<'; break;
3049 case 34: /* Page Down */ ch = '\u001B>'; break;
3050 case 37: /* Left */ ch = '\u001Bb'; break;
3051 case 38: /* Up */ ch = '\u001Bp'; break;
3052 case 39: /* Right */ ch = '\u001Bf'; break;
3053 case 40: /* Down */ ch = '\u001Bn'; break;
3054 case 46: /* Delete */ ch = '\u001Bd'; break;
3057 } else if (event.shiftKey && !event.ctrlKey &&
3058 !event.altKey && !event.metaKey) {
3060 case 33: /* Page Up */ this.scrollBack(); return;
3061 case 34: /* Page Down */ this.scrollFore(); return;
3065 if (ch == undefined) {
3067 case 8: /* Backspace */ ch = '\u007f'; break;
3068 case 9: /* Tab */ ch = '\u0009'; break;
3069 case 10: /* Return */ ch = '\u000A'; break;
3070 case 13: /* Enter */ ch = this.crLfMode ?
3071 '\r\n' : '\r'; break;
3072 case 16: /* Shift */ return;
3073 case 17: /* Ctrl */ return;
3074 case 18: /* Alt */ return;
3075 case 19: /* Break */ return;
3076 case 20: /* Caps Lock */ return;
3077 case 27: /* Escape */ ch = '\u001B'; break;
3078 case 33: /* Page Up */ ch = '\u001B[5~'; break;
3079 case 34: /* Page Down */ ch = '\u001B[6~'; break;
3080 case 35: /* End */ ch = '\u001BOF'; break;
3081 case 36: /* Home */ ch = '\u001BOH'; break;
3082 case 37: /* Left */ ch = this.cursorKeyMode ?
3083 '\u001BOD' : '\u001B[D'; break;
3084 case 38: /* Up */ ch = this.cursorKeyMode ?
3085 '\u001BOA' : '\u001B[A'; break;
3086 case 39: /* Right */ ch = this.cursorKeyMode ?
3087 '\u001BOC' : '\u001B[C'; break;
3088 case 40: /* Down */ ch = this.cursorKeyMode ?
3089 '\u001BOB' : '\u001B[B'; break;
3090 case 45: /* Insert */ ch = '\u001B[2~'; break;
3091 case 46: /* Delete */ ch = '\u001B[3~'; break;
3092 case 91: /* Left Window */ return;
3093 case 92: /* Right Window */ return;
3094 case 93: /* Select */ return;
3095 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
3096 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
3097 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
3098 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
3099 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
3100 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
3101 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
3102 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
3103 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
3104 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
3105 case 106: /* * */ ch = this.applyModifiers(42, event); break;
3106 case 107: /* + */ ch = this.applyModifiers(43, event); break;
3107 case 109: /* - */ ch = this.applyModifiers(45, event); break;
3108 case 110: /* . */ ch = this.applyModifiers(46, event); break;
3109 case 111: /* / */ ch = this.applyModifiers(47, event); break;
3110 case 112: /* F1 */ ch = '\u001BOP'; break;
3111 case 113: /* F2 */ ch = '\u001BOQ'; break;
3112 case 114: /* F3 */ ch = '\u001BOR'; break;
3113 case 115: /* F4 */ ch = '\u001BOS'; break;
3114 case 116: /* F5 */ ch = '\u001B[15~'; break;
3115 case 117: /* F6 */ ch = '\u001B[17~'; break;
3116 case 118: /* F7 */ ch = '\u001B[18~'; break;
3117 case 119: /* F8 */ ch = '\u001B[19~'; break;
3118 case 120: /* F9 */ ch = '\u001B[20~'; break;
3119 case 121: /* F10 */ ch = '\u001B[21~'; break;
3120 case 122: /* F11 */ ch = '\u001B[23~'; break;
3121 case 123: /* F12 */ ch = '\u001B[24~'; break;
3122 case 144: /* Num Lock */ return;
3123 case 145: /* Scroll Lock */ return;
3124 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
3125 case 187: /* = */ ch = this.applyModifiers(61, event); break;
3126 case 188: /* , */ ch = this.applyModifiers(44, event); break;
3127 case 189: /* - */ ch = this.applyModifiers(45, event); break;
3128 case 173: /* - */ ch = this.applyModifiers(45, event); break; // FF15 Patch
3129 case 190: /* . */ ch = this.applyModifiers(46, event); break;
3130 case 191: /* / */ ch = this.applyModifiers(47, event); break;
3131 // Conflicts with dead key " on Swiss keyboards
3132 //case 192: /* ` */ ch = this.applyModifiers(96, event); break;
3133 // Conflicts with dead key " on Swiss keyboards
3134 //case 219: /* [ */ ch = this.applyModifiers(91, event); break;
3135 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
3136 // Conflicts with dead key ^ and ` on Swiss keaboards
3137 // ^ and " on French keyboards
3138 //case 221: /* ] */ ch = this.applyModifiers(93, event); break;
3139 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
3142 this.scrollable.scrollTop = this.numScrollbackLines *
3143 this.cursorHeight + 1;
3147 // "ch" now contains the sequence of keycodes to send. But we might still
3148 // have to apply the effects of modifier keys.
3149 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
3150 var start, digit, part1, part2;
3151 if ((start = ch.substr(0, 2)) == '\u001B[') {
3153 part1.length < ch.length &&
3154 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
3155 part1 = ch.substr(0, part1.length + 1);
3157 part2 = ch.substr(part1.length);
3158 if (part1.length > 2) {
3161 } else if (start == '\u001BO') {
3163 part2 = ch.substr(2);
3165 if (part1 != undefined) {
3167 ((event.shiftKey ? 1 : 0) +
3168 (event.altKey|event.metaKey ? 2 : 0) +
3169 (event.ctrlKey ? 4 : 0)) +
3171 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
3176 if (this.menu.style.visibility == 'hidden') {
3177 // this.vt100('R: c=');
3178 // for (var i = 0; i < ch.length; i++)
3179 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
3180 // this.vt100('\r\n');
3181 this.keysPressed(ch);
3185 VT100.prototype.inspect = function(o, d) {
3186 if (d == undefined) {
3190 if (typeof o == 'object' && ++d < 2) {
3193 rc += this.spaces(d * 2) + i + ' -> ';
3195 rc += this.inspect(o[i], d);
3197 rc += '?' + '?' + '?\r\n';
3202 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
3207 VT100.prototype.checkComposedKeys = function(event) {
3208 // Composed keys (at least on Linux) do not generate normal events.
3209 // Instead, they get entered into the text field. We normally catch
3210 // this on the next keyup event.
3211 var s = this.input.value;
3213 this.input.value = '';
3214 if (this.menu.style.visibility == 'hidden') {
3215 this.keysPressed(s);
3220 VT100.prototype.fixEvent = function(event) {
3221 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
3222 // is used as a second-level selector, clear the modifier bits before
3223 // handling the event.
3224 if (event.ctrlKey && event.altKey) {
3226 fake.charCode = event.charCode;
3227 fake.keyCode = event.keyCode;
3228 fake.ctrlKey = false;
3229 fake.shiftKey = event.shiftKey;
3230 fake.altKey = false;
3231 fake.metaKey = event.metaKey;
3235 // Some browsers fail to translate keys, if both shift and alt/meta is
3236 // pressed at the same time. We try to translate those cases, but that
3237 // only works for US keyboard layouts.
3238 if (event.shiftKey) {
3241 switch (this.lastNormalKeyDownEvent.keyCode) {
3242 case 39: /* ' -> " */ u = 39; s = 34; break;
3243 case 44: /* , -> < */ u = 44; s = 60; break;
3244 case 45: /* - -> _ */ u = 45; s = 95; break;
3245 case 46: /* . -> > */ u = 46; s = 62; break;
3246 case 47: /* / -> ? */ u = 47; s = 63; break;
3248 case 48: /* 0 -> ) */ u = 48; s = 41; break;
3249 case 49: /* 1 -> ! */ u = 49; s = 33; break;
3250 case 50: /* 2 -> @ */ u = 50; s = 64; break;
3251 case 51: /* 3 -> # */ u = 51; s = 35; break;
3252 case 52: /* 4 -> $ */ u = 52; s = 36; break;
3253 case 53: /* 5 -> % */ u = 53; s = 37; break;
3254 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
3255 case 55: /* 7 -> & */ u = 55; s = 38; break;
3256 case 56: /* 8 -> * */ u = 56; s = 42; break;
3257 case 57: /* 9 -> ( */ u = 57; s = 40; break;
3259 case 59: /* ; -> : */ u = 59; s = 58; break;
3260 case 61: /* = -> + */ u = 61; s = 43; break;
3261 case 91: /* [ -> { */ u = 91; s = 123; break;
3262 case 92: /* \ -> | */ u = 92; s = 124; break;
3263 case 93: /* ] -> } */ u = 93; s = 125; break;
3264 case 96: /* ` -> ~ */ u = 96; s = 126; break;
3266 case 109: /* - -> _ */ u = 45; s = 95; break;
3267 case 111: /* / -> ? */ u = 47; s = 63; break;
3269 case 186: /* ; -> : */ u = 59; s = 58; break;
3270 case 187: /* = -> + */ u = 61; s = 43; break;
3271 case 188: /* , -> < */ u = 44; s = 60; break;
3272 case 189: /* - -> _ */ u = 45; s = 95; break;
3273 case 173: /* - -> _ */ u = 45; s = 95; break; // FF15 Patch
3274 case 190: /* . -> > */ u = 46; s = 62; break;
3275 case 191: /* / -> ? */ u = 47; s = 63; break;
3276 case 192: /* ` -> ~ */ u = 96; s = 126; break;
3277 case 219: /* [ -> { */ u = 91; s = 123; break;
3278 case 220: /* \ -> | */ u = 92; s = 124; break;
3279 case 221: /* ] -> } */ u = 93; s = 125; break;
3280 case 222: /* ' -> " */ u = 39; s = 34; break;
3283 if (s && (event.charCode == u || event.charCode == 0)) {
3286 fake.keyCode = event.keyCode;
3287 fake.ctrlKey = event.ctrlKey;
3288 fake.shiftKey = event.shiftKey;
3289 fake.altKey = event.altKey;
3290 fake.metaKey = event.metaKey;
3297 VT100.prototype.keyDown = function(event) {
3298 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
3299 // (event.shiftKey || event.ctrlKey || event.altKey ||
3300 // event.metaKey ? ', ' +
3301 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3302 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3304 this.checkComposedKeys(event);
3305 this.lastKeyPressedEvent = undefined;
3306 this.lastKeyDownEvent = undefined;
3307 this.lastNormalKeyDownEvent = event;
3309 // Swiss keyboard conflicts:
3316 // French keyoard conflicts:
3320 event.keyCode == 32 ||
3321 event.keyCode >= 48 && event.keyCode <= 57 ||
3322 event.keyCode >= 65 && event.keyCode <= 90;
3325 event.keyCode == 59 ||
3326 event.keyCode >= 96 && event.keyCode <= 105 ||
3327 event.keyCode == 107 ||
3328 event.keyCode == 192 ||
3329 event.keyCode >= 219 && event.keyCode <= 221 ||
3330 event.keyCode == 223 ||
3331 event.keyCode == 226;
3334 event.keyCode == 61 ||
3335 event.keyCode == 106 ||
3336 event.keyCode >= 109 && event.keyCode <= 111 ||
3337 event.keyCode >= 186 && event.keyCode <= 191 ||
3338 event.keyCode == 222 ||
3339 event.keyCode == 252;
3341 if (navigator.appName == 'Konqueror') {
3342 normalKey |= event.keyCode < 128;
3347 // We normally prefer to look at keypress events, as they perform the
3348 // translation from keyCode to charCode. This is important, as the
3349 // translation is locale-dependent.
3350 // But for some keys, we must intercept them during the keydown event,
3351 // as they would otherwise get interpreted by the browser.
3352 // Even, when doing all of this, there are some keys that we can never
3353 // intercept. This applies to some of the menu navigation keys in IE.
3354 // In fact, we see them, but we cannot stop IE from seeing them, too.
3355 if ((event.charCode || event.keyCode) &&
3356 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3358 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3359 // interpret this sequence ourselves, as some keyboard layouts use
3360 // it for second-level layouts.
3361 !(event.ctrlKey && event.altKey)) ||
3362 this.catchModifiersEarly && normalKey && !alphNumKey &&
3363 (event.ctrlKey || event.altKey || event.metaKey) ||
3365 this.lastKeyDownEvent = event;
3367 fake.ctrlKey = event.ctrlKey;
3368 fake.shiftKey = event.shiftKey;
3369 fake.altKey = event.altKey;
3370 fake.metaKey = event.metaKey;
3372 fake.charCode = event.keyCode;
3376 fake.keyCode = event.keyCode;
3377 if (!alphNumKey && event.shiftKey) {
3378 fake = this.fixEvent(fake);
3382 this.handleKey(fake);
3383 this.lastNormalKeyDownEvent = undefined;
3386 // For non-IE browsers
3387 event.stopPropagation();
3388 event.preventDefault();
3393 event.cancelBubble = true;
3394 event.returnValue = false;
3404 VT100.prototype.keyPressed = function(event) {
3405 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3406 // (event.shiftKey || event.ctrlKey || event.altKey ||
3407 // event.metaKey ? ', ' +
3408 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3409 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3411 if (this.lastKeyDownEvent) {
3412 // If we already processed the key on keydown, do not process it
3413 // again here. Ideally, the browser should not even have generated a
3414 // keypress event in this case. But that does not appear to always work.
3415 this.lastKeyDownEvent = undefined;
3417 this.handleKey(event.altKey || event.metaKey
3418 ? this.fixEvent(event) : event);
3422 // For non-IE browsers
3423 event.preventDefault();
3429 event.cancelBubble = true;
3430 event.returnValue = false;
3435 this.lastNormalKeyDownEvent = undefined;
3436 this.lastKeyPressedEvent = event;
3440 VT100.prototype.keyUp = function(event) {
3441 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3442 // (event.shiftKey || event.ctrlKey || event.altKey ||
3443 // event.metaKey ? ', ' +
3444 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3445 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3447 if (this.lastKeyPressedEvent) {
3448 // The compose key on Linux occasionally confuses the browser and keeps
3449 // inserting bogus characters into the input field, even if just a regular
3450 // key has been pressed. Detect this case and drop the bogus characters.
3452 event.srcElement).value = '';
3454 // This is usually were we notice that a key has been composed and
3455 // thus failed to generate normal events.
3456 this.checkComposedKeys(event);
3458 // Some browsers don't report keypress events if ctrl or alt is pressed
3459 // for non-alphanumerical keys. Patch things up for now, but in the
3460 // future we will catch these keys earlier (in the keydown handler).
3461 if (this.lastNormalKeyDownEvent) {
3462 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3463 this.catchModifiersEarly = true;
3465 event.keyCode == 32 ||
3466 // Conflicts with dead key ~ (code 50) on French keyboards
3467 //event.keyCode >= 48 && event.keyCode <= 57 ||
3468 event.keyCode >= 48 && event.keyCode <= 49 ||
3469 event.keyCode >= 51 && event.keyCode <= 57 ||
3470 event.keyCode >= 65 && event.keyCode <= 90;
3473 event.keyCode == 50 ||
3474 event.keyCode >= 96 && event.keyCode <= 105;
3477 event.keyCode == 59 || event.keyCode == 61 ||
3478 event.keyCode == 106 || event.keyCode == 107 ||
3479 event.keyCode >= 109 && event.keyCode <= 111 ||
3480 event.keyCode >= 186 && event.keyCode <= 192 ||
3481 event.keyCode >= 219 && event.keyCode <= 223 ||
3482 event.keyCode == 252;
3484 fake.ctrlKey = event.ctrlKey;
3485 fake.shiftKey = event.shiftKey;
3486 fake.altKey = event.altKey;
3487 fake.metaKey = event.metaKey;
3489 fake.charCode = event.keyCode;
3493 fake.keyCode = event.keyCode;
3494 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3495 fake = this.fixEvent(fake);
3498 this.lastNormalKeyDownEvent = undefined;
3499 this.handleKey(fake);
3505 event.cancelBubble = true;
3506 event.returnValue = false;
3511 this.lastKeyDownEvent = undefined;
3512 this.lastKeyPressedEvent = undefined;
3516 VT100.prototype.animateCursor = function(inactive) {
3517 if (!this.cursorInterval) {
3518 this.cursorInterval = setInterval(
3521 vt100.animateCursor();
3523 // Use this opportunity to check whether the user entered a composed
3524 // key, or whether somebody pasted text into the textfield.
3525 vt100.checkComposedKeys();
3529 if (inactive != undefined || this.cursor.className != 'inactive') {
3531 this.cursor.className = 'inactive';
3533 if (this.blinkingCursor) {
3534 this.cursor.className = this.cursor.className == 'bright'
3537 this.cursor.className = 'bright';
3543 VT100.prototype.blurCursor = function() {
3544 this.animateCursor(true);
3547 VT100.prototype.focusCursor = function() {
3548 this.animateCursor(false);
3551 VT100.prototype.flashScreen = function() {
3552 this.isInverted = !this.isInverted;
3553 this.refreshInvertedState();
3554 this.isInverted = !this.isInverted;
3555 setTimeout(function(vt100) {
3557 vt100.refreshInvertedState();
3562 VT100.prototype.beep = function() {
3563 if (this.visualBell) {
3570 this.beeper.src = 'beep.wav';
3577 VT100.prototype.bs = function() {
3578 if (this.cursorX > 0) {
3579 this.gotoXY(this.cursorX - 1, this.cursorY);
3580 this.needWrap = false;
3584 VT100.prototype.ht = function(count) {
3585 if (count == undefined) {
3588 var cx = this.cursorX;
3589 while (count-- > 0) {
3590 while (cx++ < this.terminalWidth) {
3591 var tabState = this.userTabStop[cx];
3592 if (tabState == false) {
3593 // Explicitly cleared tab stop
3595 } else if (tabState) {
3596 // Explicitly set tab stop
3599 // Default tab stop at each eighth column
3606 if (cx > this.terminalWidth - 1) {
3607 cx = this.terminalWidth - 1;
3609 if (cx != this.cursorX) {
3610 this.gotoXY(cx, this.cursorY);
3614 VT100.prototype.rt = function(count) {
3615 if (count == undefined) {
3618 var cx = this.cursorX;
3619 while (count-- > 0) {
3621 var tabState = this.userTabStop[cx];
3622 if (tabState == false) {
3623 // Explicitly cleared tab stop
3625 } else if (tabState) {
3626 // Explicitly set tab stop
3629 // Default tab stop at each eighth column
3639 if (cx != this.cursorX) {
3640 this.gotoXY(cx, this.cursorY);
3644 VT100.prototype.cr = function() {
3645 this.gotoXY(0, this.cursorY);
3646 this.needWrap = false;
3649 VT100.prototype.lf = function(count) {
3650 if (count == undefined) {
3653 if (count > this.terminalHeight) {
3654 count = this.terminalHeight;
3660 while (count-- > 0) {
3661 if (this.cursorY == this.bottom - 1) {
3662 this.scrollRegion(0, this.top + 1,
3663 this.terminalWidth, this.bottom - this.top - 1,
3664 0, -1, this.color, this.style);
3666 } else if (this.cursorY < this.terminalHeight - 1) {
3667 this.gotoXY(this.cursorX, this.cursorY + 1);
3672 VT100.prototype.ri = function(count) {
3673 if (count == undefined) {
3676 if (count > this.terminalHeight) {
3677 count = this.terminalHeight;
3683 while (count-- > 0) {
3684 if (this.cursorY == this.top) {
3685 this.scrollRegion(0, this.top,
3686 this.terminalWidth, this.bottom - this.top - 1,
3687 0, 1, this.color, this.style);
3688 } else if (this.cursorY > 0) {
3689 this.gotoXY(this.cursorX, this.cursorY - 1);
3692 this.needWrap = false;
3695 VT100.prototype.respondID = function() {
3696 this.respondString += '\u001B[?6c';
3699 VT100.prototype.respondSecondaryDA = function() {
3700 this.respondString += '\u001B[>0;0;0c';
3704 VT100.prototype.updateStyle = function() {
3706 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3707 this.style = 'text-decoration: underline;';
3709 var bg = (this.attr >> 4) & 0xF;
3710 var fg = this.attr & 0xF;
3711 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3716 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3717 fg = 8; // Dark grey
3718 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3720 this.style = 'font-weight: bold;';
3722 if (this.attr & 0x1000 /* ATTR_BLINK */) {
3723 this.style = 'text-decoration: blink;';
3725 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3728 VT100.prototype.setAttrColors = function(attr) {
3729 if (attr != this.attr) {
3735 VT100.prototype.saveCursor = function() {
3736 this.savedX[this.currentScreen] = this.cursorX;
3737 this.savedY[this.currentScreen] = this.cursorY;
3738 this.savedAttr[this.currentScreen] = this.attr;
3739 this.savedUseGMap = this.useGMap;
3740 for (var i = 0; i < 4; i++) {
3741 this.savedGMap[i] = this.GMap[i];
3743 this.savedValid[this.currentScreen] = true;
3746 VT100.prototype.restoreCursor = function() {
3747 if (!this.savedValid[this.currentScreen]) {
3750 this.attr = this.savedAttr[this.currentScreen];
3752 this.useGMap = this.savedUseGMap;
3753 for (var i = 0; i < 4; i++) {
3754 this.GMap[i] = this.savedGMap[i];
3756 this.translate = this.GMap[this.useGMap];
3757 this.needWrap = false;
3758 this.gotoXY(this.savedX[this.currentScreen],
3759 this.savedY[this.currentScreen]);
3762 VT100.prototype.getTransformName = function() {
3763 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3764 for (var i = 0; i < styles.length; ++i) {
3765 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3772 VT100.prototype.getTransformStyle = function(transform, scale) {
3773 return scale && scale != 1.0
3774 ? transform == 'filter'
3775 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3776 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3777 "sizingMethod='auto expand')"
3778 : 'translateX(-50%) ' +
3779 'scaleX(' + (1.0/scale) + ') ' +
3784 VT100.prototype.set80_132Mode = function(state) {
3785 var transform = this.getTransformName();
3787 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3791 this.getTransformStyle(transform, 1.65):'';
3792 this.console[this.currentScreen].style[transform] = style;
3793 this.cursor.style[transform] = style;
3794 this.space.style[transform] = style;
3795 this.scale = state ? 1.65 : 1.0;
3796 if (transform == 'filter') {
3797 this.console[this.currentScreen].style.width = state ? '165%' : '';
3803 VT100.prototype.setMode = function(state) {
3804 for (var i = 0; i <= this.npar; i++) {
3805 if (this.isQuestionMark) {
3806 switch (this.par[i]) {
3807 case 1: this.cursorKeyMode = state; break;
3808 case 3: this.set80_132Mode(state); break;
3809 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3810 case 6: this.offsetMode = state; break;
3811 case 7: this.autoWrapMode = state; break;
3813 case 9: this.mouseReporting = state; break;
3814 case 25: this.cursorNeedsShowing = state;
3815 if (state) { this.showCursor(); }
3816 else { this.hideCursor(); } break;
3819 case 47: this.enableAlternateScreen(state); break;
3823 switch (this.par[i]) {
3824 case 3: this.dispCtrl = state; break;
3825 case 4: this.insertMode = state; break;
3826 case 20:this.crLfMode = state; break;
3833 VT100.prototype.statusReport = function() {
3834 // Ready and operational.
3835 this.respondString += '\u001B[0n';
3838 VT100.prototype.cursorReport = function() {
3839 this.respondString += '\u001B[' +
3840 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3842 (this.cursorX + 1) +
3846 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3847 // Changing of cursor color is not implemented.
3850 VT100.prototype.openPrinterWindow = function() {
3853 if (!this.printWin || this.printWin.closed) {
3854 this.printWin = window.open('', 'print-output',
3855 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3856 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3857 this.printWin.document.body.innerHTML =
3858 '<link rel="stylesheet" href="' +
3859 document.location.protocol + '//' + document.location.host +
3860 document.location.pathname.replace(/[^/]*$/, '') +
3861 'print-styles.css" type="text/css">\n' +
3862 '<div id="options"><input id="autoprint" type="checkbox"' +
3863 (this.autoprint ? ' checked' : '') + '>' +
3864 'Automatically, print page(s) when job is ready' +
3865 '</input></div>\n' +
3866 '<div id="spacer"><input type="checkbox"> </input></div>' +
3867 '<pre id="print"></pre>\n';
3868 var autoprint = this.printWin.document.getElementById('autoprint');
3869 this.addListener(autoprint, 'click',
3870 (function(vt100, autoprint) {
3872 vt100.autoprint = autoprint.checked;
3873 vt100.storeUserSettings();
3876 })(this, autoprint));
3877 this.printWin.document.title = 'ShellInABox Printer Output';
3880 // Maybe, a popup blocker prevented us from working. Better catch the
3881 // exception, so that we won't break the entire terminal session. The
3882 // user probably needs to disable the blocker first before retrying the
3886 rc &= this.printWin && !this.printWin.closed &&
3887 (this.printWin.innerWidth ||
3888 this.printWin.document.documentElement.clientWidth ||
3889 this.printWin.document.body.clientWidth) > 1;
3891 if (!rc && this.printing == 100) {
3892 // Different popup blockers work differently. We try to detect a couple
3893 // of common methods. And then we retry again a brief amount later, as
3894 // false positives are otherwise possible. If we are sure that there is
3895 // a popup blocker in effect, we alert the user to it. This is helpful
3896 // as some popup blockers have minimal or no UI, and the user might not
3897 // notice that they are missing the popup. In any case, we only show at
3898 // most one message per print job.
3899 this.printing = true;
3900 setTimeout((function(win) {
3902 if (!win || win.closed ||
3904 win.document.documentElement.clientWidth ||
3905 win.document.body.clientWidth) <= 1) {
3906 alert('Attempted to print, but a popup blocker ' +
3907 'prevented the printer window from opening');
3910 })(this.printWin), 2000);
3915 VT100.prototype.sendToPrinter = function(s) {
3916 this.openPrinterWindow();
3918 var doc = this.printWin.document;
3919 var print = doc.getElementById('print');
3920 if (print.lastChild && print.lastChild.nodeName == '#text') {
3921 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3923 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3926 // There probably was a more aggressive popup blocker that prevented us
3927 // from accessing the printer windows.
3931 VT100.prototype.sendControlToPrinter = function(ch) {
3932 // We get called whenever doControl() is active. But for the printer, we
3933 // only implement a basic line printer that doesn't understand most of
3934 // the escape sequences of the VT100 terminal. In fact, the only escape
3935 // sequence that we really need to recognize is '^[[5i' for turning the
3941 this.openPrinterWindow();
3942 var doc = this.printWin.document;
3943 var print = doc.getElementById('print');
3944 var chars = print.lastChild &&
3945 print.lastChild.nodeName == '#text' ?
3946 print.lastChild.textContent.length : 0;
3947 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3954 this.openPrinterWindow();
3955 var pageBreak = this.printWin.document.createElement('div');
3956 pageBreak.className = 'pagebreak';
3957 pageBreak.innerHTML = '<hr />';
3958 this.printWin.document.getElementById('print').appendChild(pageBreak);
3962 this.openPrinterWindow();
3963 var lineBreak = this.printWin.document.createElement('br');
3964 this.printWin.document.getElementById('print').appendChild(lineBreak);
3968 this.isEsc = 1 /* ESesc */;
3971 switch (this.isEsc) {
3973 this.isEsc = 0 /* ESnormal */;
3976 this.isEsc = 2 /* ESsquare */;
3982 case 2 /* ESsquare */:
3984 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3985 0, 0, 0, 0, 0, 0, 0, 0 ];
3986 this.isEsc = 3 /* ESgetpars */;
3987 this.isQuestionMark = ch == 0x3F /*?*/;
3988 if (this.isQuestionMark) {
3992 case 3 /* ESgetpars */:
3993 if (ch == 0x3B /*;*/) {
3996 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3997 var par = this.par[this.npar];
3998 if (par == undefined) {
4001 this.par[this.npar] = 10*par + (ch & 0xF);
4004 this.isEsc = 4 /* ESgotpars */;
4007 case 4 /* ESgotpars */:
4008 this.isEsc = 0 /* ESnormal */;
4009 if (this.isQuestionMark) {
4014 this.csii(this.par[0]);
4021 this.isEsc = 0 /* ESnormal */;
4027 // There probably was a more aggressive popup blocker that prevented us
4028 // from accessing the printer windows.
4032 VT100.prototype.csiAt = function(number) {
4037 if (number > this.terminalWidth - this.cursorX) {
4038 number = this.terminalWidth - this.cursorX;
4040 this.scrollRegion(this.cursorX, this.cursorY,
4041 this.terminalWidth - this.cursorX - number, 1,
4042 number, 0, this.color, this.style);
4043 this.needWrap = false;
4046 VT100.prototype.csii = function(number) {
4049 case 0: // Print Screen
4052 case 4: // Stop printing
4054 if (this.printing && this.printWin && !this.printWin.closed) {
4055 var print = this.printWin.document.getElementById('print');
4056 while (print.lastChild &&
4057 print.lastChild.tagName == 'DIV' &&
4058 print.lastChild.className == 'pagebreak') {
4059 // Remove trailing blank pages
4060 print.removeChild(print.lastChild);
4062 if (this.autoprint) {
4063 this.printWin.print();
4068 this.printing = false;
4070 case 5: // Start printing
4071 if (!this.printing && this.printWin && !this.printWin.closed) {
4072 this.printWin.document.getElementById('print').innerHTML = '';
4074 this.printing = 100;
4081 VT100.prototype.csiJ = function(number) {
4083 case 0: // Erase from cursor to end of display
4084 this.clearRegion(this.cursorX, this.cursorY,
4085 this.terminalWidth - this.cursorX, 1,
4086 this.color, this.style);
4087 if (this.cursorY < this.terminalHeight-2) {
4088 this.clearRegion(0, this.cursorY+1,
4089 this.terminalWidth, this.terminalHeight-this.cursorY-1,
4090 this.color, this.style);
4093 case 1: // Erase from start to cursor
4094 if (this.cursorY > 0) {
4095 this.clearRegion(0, 0,
4096 this.terminalWidth, this.cursorY,
4097 this.color, this.style);
4099 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4100 this.color, this.style);
4102 case 2: // Erase whole display
4103 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
4104 this.color, this.style);
4112 VT100.prototype.csiK = function(number) {
4114 case 0: // Erase from cursor to end of line
4115 this.clearRegion(this.cursorX, this.cursorY,
4116 this.terminalWidth - this.cursorX, 1,
4117 this.color, this.style);
4119 case 1: // Erase from start of line to cursor
4120 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4121 this.color, this.style);
4123 case 2: // Erase whole line
4124 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
4125 this.color, this.style);
4133 VT100.prototype.csiL = function(number) {
4134 // Open line by inserting blank line(s)
4135 if (this.cursorY >= this.bottom) {
4141 if (number > this.bottom - this.cursorY) {
4142 number = this.bottom - this.cursorY;
4144 this.scrollRegion(0, this.cursorY,
4145 this.terminalWidth, this.bottom - this.cursorY - number,
4146 0, number, this.color, this.style);
4150 VT100.prototype.csiM = function(number) {
4151 // Delete line(s), scrolling up the bottom of the screen.
4152 if (this.cursorY >= this.bottom) {
4158 if (number > this.bottom - this.cursorY) {
4159 number = bottom - cursorY;
4161 this.scrollRegion(0, this.cursorY + number,
4162 this.terminalWidth, this.bottom - this.cursorY - number,
4163 0, -number, this.color, this.style);
4167 VT100.prototype.csim = function() {
4168 for (var i = 0; i <= this.npar; i++) {
4169 switch (this.par[i]) {
4170 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
4171 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
4172 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
4173 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
4174 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
4175 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
4177 this.translate = this.GMap[this.useGMap];
4178 this.dispCtrl = false;
4179 this.toggleMeta = false;
4182 this.translate = this.CodePage437Map;
4183 this.dispCtrl = true;
4184 this.toggleMeta = false;
4187 this.translate = this.CodePage437Map;
4188 this.dispCtrl = true;
4189 this.toggleMeta = true;
4192 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
4193 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
4194 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
4195 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
4196 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
4197 0x0200 /* ATTR_UNDERLINE */; break;
4198 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
4199 case 49: this.attr |= 0xF0; break;
4201 if (this.par[i] >= 30 && this.par[i] <= 37) {
4202 var fg = this.par[i] - 30;
4203 this.attr = (this.attr & ~0x0F) | fg;
4204 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
4205 var bg = this.par[i] - 40;
4206 this.attr = (this.attr & ~0xF0) | (bg << 4);
4214 VT100.prototype.csiP = function(number) {
4215 // Delete character(s) following cursor
4219 if (number > this.terminalWidth - this.cursorX) {
4220 number = this.terminalWidth - this.cursorX;
4222 this.scrollRegion(this.cursorX + number, this.cursorY,
4223 this.terminalWidth - this.cursorX - number, 1,
4224 -number, 0, this.color, this.style);
4228 VT100.prototype.csiX = function(number) {
4229 // Clear characters following cursor
4233 if (number > this.terminalWidth - this.cursorX) {
4234 number = this.terminalWidth - this.cursorX;
4236 this.clearRegion(this.cursorX, this.cursorY, number, 1,
4237 this.color, this.style);
4241 VT100.prototype.settermCommand = function() {
4242 // Setterm commands are not implemented
4245 VT100.prototype.doControl = function(ch) {
4246 if (this.printing) {
4247 this.sendControlToPrinter(ch);
4252 case 0x00: /* ignored */ break;
4253 case 0x08: this.bs(); break;
4254 case 0x09: this.ht(); break;
4258 case 0x84: this.lf(); if (!this.crLfMode) break;
4259 case 0x0D: this.cr(); break;
4260 case 0x85: this.cr(); this.lf(); break;
4261 case 0x0E: this.useGMap = 1;
4262 this.translate = this.GMap[1];
4263 this.dispCtrl = true; break;
4264 case 0x0F: this.useGMap = 0;
4265 this.translate = this.GMap[0];
4266 this.dispCtrl = false; break;
4268 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
4269 case 0x1B: this.isEsc = 1 /* ESesc */; break;
4270 case 0x7F: /* ignored */ break;
4271 case 0x88: this.userTabStop[this.cursorX] = true; break;
4272 case 0x8D: this.ri(); break;
4273 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
4274 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
4275 case 0x9A: this.respondID(); break;
4276 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
4277 case 0x07: if (this.isEsc != 17 /* EStitle */) {
4281 default: switch (this.isEsc) {
4283 this.isEsc = 0 /* ESnormal */;
4285 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
4286 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
4288 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
4290 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
4292 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
4293 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
4294 /*7*/ case 0x37: this.saveCursor(); break;
4295 /*8*/ case 0x38: this.restoreCursor(); break;
4296 /*>*/ case 0x3E: this.applKeyMode = false; break;
4297 /*=*/ case 0x3D: this.applKeyMode = true; break;
4298 /*D*/ case 0x44: this.lf(); break;
4299 /*E*/ case 0x45: this.cr(); this.lf(); break;
4300 /*M*/ case 0x4D: this.ri(); break;
4301 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
4302 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
4303 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
4304 /*Z*/ case 0x5A: this.respondID(); break;
4305 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
4306 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
4307 /*c*/ case 0x63: this.reset(); break;
4308 /*g*/ case 0x67: this.flashScreen(); break;
4312 case 15 /* ESnonstd */:
4316 /*2*/ case 0x32: this.isEsc = 17 /* EStitle */; this.titleString = ''; break;
4317 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4318 this.isEsc = 16 /* ESpalette */; break;
4319 /*R*/ case 0x52: // Palette support is not implemented
4320 this.isEsc = 0 /* ESnormal */; break;
4321 default: this.isEsc = 0 /* ESnormal */; break;
4324 case 16 /* ESpalette */:
4325 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4326 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4327 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4328 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
4330 if (this.npar == 7) {
4331 // Palette support is not implemented
4332 this.isEsc = 0 /* ESnormal */;
4335 this.isEsc = 0 /* ESnormal */;
4338 case 2 /* ESsquare */:
4340 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
4341 0, 0, 0, 0, 0, 0, 0, 0 ];
4342 this.isEsc = 3 /* ESgetpars */;
4343 /*[*/ if (ch == 0x5B) { // Function key
4344 this.isEsc = 6 /* ESfunckey */;
4347 /*?*/ this.isQuestionMark = ch == 0x3F;
4348 if (this.isQuestionMark) {
4353 case 5 /* ESdeviceattr */:
4354 case 3 /* ESgetpars */:
4355 /*;*/ if (ch == 0x3B) {
4358 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4359 var par = this.par[this.npar];
4360 if (par == undefined) {
4363 this.par[this.npar] = 10*par + (ch & 0xF);
4365 } else if (this.isEsc == 5 /* ESdeviceattr */) {
4367 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
4368 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
4369 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
4370 /*p*/ case 0x70: /* set pointer mode resource value */ break;
4373 this.isEsc = 0 /* ESnormal */;
4376 this.isEsc = 4 /* ESgotpars */;
4379 case 4 /* ESgotpars */:
4380 this.isEsc = 0 /* ESnormal */;
4381 if (this.isQuestionMark) {
4383 /*h*/ case 0x68: this.setMode(true); break;
4384 /*l*/ case 0x6C: this.setMode(false); break;
4385 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
4388 this.isQuestionMark = false;
4392 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
4393 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
4395 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
4396 /*A*/ case 0x41: this.gotoXY(this.cursorX,
4397 this.cursorY - (this.par[0] ? this.par[0] : 1));
4400 /*e*/ case 0x65: this.gotoXY(this.cursorX,
4401 this.cursorY + (this.par[0] ? this.par[0] : 1));
4404 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4405 this.cursorY); break;
4406 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4407 this.cursorY); break;
4408 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4410 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4412 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
4414 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
4415 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
4416 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
4417 /*i*/ case 0x69: this.csii(this.par[0]); break;
4418 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
4419 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
4420 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
4421 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
4422 /*m*/ case 0x6D: this.csim(); break;
4423 /*P*/ case 0x50: this.csiP(this.par[0]); break;
4424 /*X*/ case 0x58: this.csiX(this.par[0]); break;
4425 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4426 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4427 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4428 /*g*/ case 0x67: if (this.par[0] == 0) {
4429 this.userTabStop[this.cursorX] = false;
4430 } else if (this.par[0] == 2 || this.par[0] == 3) {
4431 this.userTabStop = [ ];
4432 for (var i = 0; i < this.terminalWidth; i++) {
4433 this.userTabStop[i] = false;
4437 /*h*/ case 0x68: this.setMode(true); break;
4438 /*l*/ case 0x6C: this.setMode(false); break;
4439 /*n*/ case 0x6E: switch (this.par[0]) {
4440 case 5: this.statusReport(); break;
4441 case 6: this.cursorReport(); break;
4445 /*q*/ case 0x71: // LED control not implemented
4447 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4448 var b = this.par[1] ? this.par[1]
4449 : this.terminalHeight;
4450 if (t < b && b <= this.terminalHeight) {
4456 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4457 if (c > this.terminalWidth * this.terminalHeight) {
4458 c = this.terminalWidth * this.terminalHeight;
4461 lineBuf += this.lastCharacter;
4464 /*s*/ case 0x73: this.saveCursor(); break;
4465 /*u*/ case 0x75: this.restoreCursor(); break;
4466 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4467 /*]*/ case 0x5D: this.settermCommand(); break;
4471 case 12 /* ESbang */:
4475 this.isEsc = 0 /* ESnormal */;
4477 case 13 /* ESpercent */:
4478 this.isEsc = 0 /* ESnormal */;
4480 /*@*/ case 0x40: this.utfEnabled = false; break;
4482 /*8*/ case 0x38: this.utfEnabled = true; break;
4486 case 6 /* ESfunckey */:
4487 this.isEsc = 0 /* ESnormal */; break;
4488 case 7 /* EShash */:
4489 this.isEsc = 0 /* ESnormal */;
4490 /*8*/ if (ch == 0x38) {
4491 // Screen alignment test not implemented
4494 case 8 /* ESsetG0 */:
4495 case 9 /* ESsetG1 */:
4496 case 10 /* ESsetG2 */:
4497 case 11 /* ESsetG3 */:
4498 var g = this.isEsc - 8 /* ESsetG0 */;
4499 this.isEsc = 0 /* ESnormal */;
4501 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4503 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4504 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4505 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4508 if (this.useGMap == g) {
4509 this.translate = this.GMap[g];
4512 case 17 /* EStitle */:
4514 if (this.titleString && this.titleString.charAt(0) == ';') {
4515 this.titleString = this.titleString.substr(1);
4516 if (this.titleString != '') {
4517 this.titleString += ' - ';
4519 this.titleString += 'Shell In A Box'
4522 window.document.title = this.titleString;
4525 this.isEsc = 0 /* ESnormal */;
4527 this.titleString += String.fromCharCode(ch);
4530 case 18 /* ESss2 */:
4531 case 19 /* ESss3 */:
4533 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4534 [this.toggleMeta ? (ch | 0x80) : ch];
4535 if ((ch & 0xFF00) == 0xF000) {
4537 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4538 this.isEsc = 0 /* ESnormal */; break;
4541 this.lastCharacter = String.fromCharCode(ch);
4542 lineBuf += this.lastCharacter;
4543 this.isEsc = 0 /* ESnormal */; break;
4545 this.isEsc = 0 /* ESnormal */; break;
4552 VT100.prototype.renderString = function(s, showCursor) {
4553 if (this.printing) {
4554 this.sendToPrinter(s);
4561 // We try to minimize the number of DOM operations by coalescing individual
4562 // characters into strings. This is a significant performance improvement.
4563 var incX = s.length;
4564 if (incX > this.terminalWidth - this.cursorX) {
4565 incX = this.terminalWidth - this.cursorX;
4569 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4572 // Minimize the number of calls to putString(), by avoiding a direct
4573 // call to this.showCursor()
4574 this.cursor.style.visibility = '';
4576 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4579 VT100.prototype.vt100 = function(s) {
4580 this.cursorNeedsShowing = this.hideCursor();
4581 this.respondString = '';
4583 for (var i = 0; i < s.length; i++) {
4584 var ch = s.charCodeAt(i);
4585 if (this.utfEnabled) {
4586 // Decode UTF8 encoded character
4588 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4589 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4590 if (--this.utfCount <= 0) {
4591 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4600 if ((ch & 0xE0) == 0xC0) {
4602 this.utfChar = ch & 0x1F;
4603 } else if ((ch & 0xF0) == 0xE0) {
4605 this.utfChar = ch & 0x0F;
4606 } else if ((ch & 0xF8) == 0xF0) {
4608 this.utfChar = ch & 0x07;
4609 } else if ((ch & 0xFC) == 0xF8) {
4611 this.utfChar = ch & 0x03;
4612 } else if ((ch & 0xFE) == 0xFC) {
4614 this.utfChar = ch & 0x01;
4624 var isNormalCharacter =
4625 (ch >= 32 && ch <= 127 || ch >= 160 ||
4626 this.utfEnabled && ch >= 128 ||
4627 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4628 (ch != 0x7F || this.dispCtrl);
4630 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4632 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4634 if ((ch & 0xFF00) == 0xF000) {
4636 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4639 if (!this.printing) {
4640 if (this.needWrap || this.insertMode) {
4642 this.renderString(lineBuf);
4646 if (this.needWrap) {
4647 this.cr(); this.lf();
4649 if (this.insertMode) {
4650 this.scrollRegion(this.cursorX, this.cursorY,
4651 this.terminalWidth - this.cursorX - 1, 1,
4652 1, 0, this.color, this.style);
4655 this.lastCharacter = String.fromCharCode(ch);
4656 lineBuf += this.lastCharacter;
4657 if (!this.printing &&
4658 this.cursorX + lineBuf.length >= this.terminalWidth) {
4659 this.needWrap = this.autoWrapMode;
4663 this.renderString(lineBuf);
4666 var expand = this.doControl(ch);
4667 if (expand.length) {
4668 var r = this.respondString;
4669 this.respondString= r + this.vt100(expand);
4674 this.renderString(lineBuf, this.cursorNeedsShowing);
4675 } else if (this.cursorNeedsShowing) {
4678 return this.respondString;
4681 VT100.prototype.Latin1Map = [
4682 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4683 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4684 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4685 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4686 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4687 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4688 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4689 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4690 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4691 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4692 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4693 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4694 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4695 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4696 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4697 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4698 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4699 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4700 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4701 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4702 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4703 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4704 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4705 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4706 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4707 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4708 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4709 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4710 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4711 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4712 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4713 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4716 VT100.prototype.VT100GraphicsMap = [
4717 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4718 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4719 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4720 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4721 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4722 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4723 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4724 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4725 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4726 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4727 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4728 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4729 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4730 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4731 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4732 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4733 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4734 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4735 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4736 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4737 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4738 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4739 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4740 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4741 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4742 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4743 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4744 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4745 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4746 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4747 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4748 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4751 VT100.prototype.CodePage437Map = [
4752 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4753 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4754 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4755 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4756 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4757 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4758 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4759 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4760 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4761 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4762 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4763 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4764 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4765 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4766 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4767 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4768 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4769 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4770 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4771 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4772 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4773 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4774 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4775 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4776 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4777 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4778 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4779 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4780 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4781 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4782 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4783 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4786 VT100.prototype.DirectToFontMap = [
4787 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4788 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4789 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4790 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4791 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4792 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4793 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4794 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4795 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4796 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4797 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4798 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4799 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4800 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4801 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4802 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4803 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4804 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4805 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4806 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4807 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4808 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4809 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4810 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4811 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4812 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4813 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4814 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4815 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4816 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4817 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4818 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4821 VT100.prototype.ctrlAction = [
4822 true, false, false, false, false, false, false, true,
4823 true, true, true, true, true, true, true, true,
4824 false, false, false, false, false, false, false, false,
4825 true, false, true, true, false, false, false, false
4828 VT100.prototype.ctrlAlways = [
4829 true, false, false, false, false, false, false, false,
4830 true, false, true, false, true, true, true, true,
4831 false, false, false, false, false, false, false, false,
4832 false, false, false, true, false, false, false, false