1 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com> All rights reserved.
3 // SPDX-License-Identifier: GPL-2.0
5 // This file contains code from shell_in_a_box.js and vt100.js
8 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
9 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License version 2 as
13 // published by the Free Software Foundation.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License along
21 // with this program; if not, write to the Free Software Foundation, Inc.,
22 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 // In addition to these license terms, the author grants the following
27 // If you modify this program, or any covered work, by linking or
28 // combining it with the OpenSSL project's OpenSSL library (or a
29 // modified version of that library), containing parts covered by the
30 // terms of the OpenSSL or SSLeay licenses, the author
31 // grants you additional permission to convey the resulting work.
32 // Corresponding Source for a non-source form of such a combination
33 // shall include the source code for the parts of OpenSSL used as well
34 // as that of the covered work.
36 // You may at your option choose to remove this additional permission from
37 // the work, or from any part of it.
39 // It is possible to build this program in a way that it loads OpenSSL
40 // libraries at run-time. If doing so, the following notices are required
41 // by the OpenSSL and SSLeay licenses:
43 // This product includes software developed by the OpenSSL Project
44 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
46 // This product includes cryptographic software written by Eric Young
47 // (eay@cryptsoft.com)
50 // The most up-to-date version of this program is always available from
51 // http://shellinabox.com
56 // The author believes that for the purposes of this license, you meet the
57 // requirements for publishing the source code, if your web server publishes
58 // the source in unmodified form (i.e. with licensing information, comments,
59 // formatting, and identifier names intact). If there are technical reasons
60 // that require you to make changes to the source code when serving the
61 // JavaScript (e.g to remove pre-processor directives from the source), these
62 // changes should be done in a reversible fashion.
64 // The author does not consider websites that reference this script in
65 // unmodified form, and web servers that serve this script in unmodified form
66 // to be derived works. As such, they are believed to be outside of the
67 // scope of this license and not subject to the rights or restrictions of the
68 // GNU General Public License.
70 // If in doubt, consult a legal professional familiar with the laws that
71 // apply in your country.
73 // #define XHR_UNITIALIZED 0
76 // #define XHR_RECEIVING 3
77 // #define XHR_LOADED 4
79 // IE does not define XMLHttpRequest by default, so we provide a suitable
81 if (typeof XMLHttpRequest == 'undefined') {
82 XMLHttpRequest = function() {
83 try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
84 try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
85 try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { }
86 try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
91 function extend(subClass, baseClass) {
92 function inheritance() { }
93 inheritance.prototype = baseClass.prototype;
94 subClass.prototype = new inheritance();
95 subClass.prototype.constructor = subClass;
96 subClass.prototype.superClass = baseClass.prototype;
99 function ShellInABox(url, container) {
100 if (url == undefined) {
101 this.rooturl = document.location.href;
102 this.url = document.location.href.replace(/[?#].*/, '');
107 if (document.location.hash != '') {
108 var hash = decodeURIComponent(document.location.hash).
110 this.nextUrl = hash.replace(/,.*/, '');
111 this.session = hash.replace(/[^,]*,/, '');
113 this.nextUrl = this.url;
116 this.pendingKeys = '';
117 this.keysInFlight = false;
118 this.connected = false;
119 this.superClass.constructor.call(this, container);
121 // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
122 // Chrome never realizes that the page has loaded.
123 setTimeout(function(shellInABox) {
125 shellInABox.sendRequest(true);
129 extend(ShellInABox, VT100);
131 ShellInABox.prototype.sessionClosed = function(msg) {
133 this.connected = false;
135 this.session = undefined;
136 if (this.cursorX > 0) {
139 this.vt100(msg || 'Session closed.');
140 this.currentRequest.abort();
142 // Revealing the "reconnect" button is commented out until we hook
143 // up the username+token auto-login mechanism to the new session:
144 //this.showReconnect(true);
149 ShellInABox.prototype.reconnect = function() {
150 this.showReconnect(false);
152 if (document.location.hash != '') {
153 // A shellinaboxd daemon launched from a CGI only allows a single
154 // session. In order to reconnect, we must reload the frame definition
155 // and obtain a new port number. As this is a different origin, we
156 // need to get enclosing page to help us.
157 parent.location = this.nextUrl;
159 if (this.url != this.nextUrl) {
160 document.location.replace(this.nextUrl);
162 this.pendingKeys = '';
163 this.keysInFlight = false;
165 this.sendRequest(true);
172 ShellInABox.prototype.sendRequest = function(init = false, request) {
173 if (request == undefined) {
174 request = new XMLHttpRequest();
176 request.open('POST', this.url + '?', true);
177 request.setRequestHeader('Cache-Control', 'no-cache');
178 request.setRequestHeader('Content-Type',
179 'application/x-www-form-urlencoded; charset=utf-8');
180 var content = 'width=' + this.terminalWidth +
181 '&height=' + this.terminalHeight +
182 (this.session ? '&session=' +
183 encodeURIComponent(this.session) : '&rooturl='+
184 encodeURIComponent(this.rooturl));
186 request.onreadystatechange = function(shellInABox) {
189 return shellInABox.onReadyStateChange(request, init);
191 shellInABox.sessionClosed();
195 ShellInABox.lastRequestSent = Date.now();
196 request.send(content);
197 this.currentRequest = request;
200 ShellInABox.prototype.onReadyStateChange = function(request, init) {
201 if (request.readyState == 4 /* XHR_LOADED */ && (this.connected || init)) {
202 if (request.status == 200) {
203 this.connected = true;
204 var response = eval('(' + request.responseText + ')');
206 this.vt100(response.data);
209 if (!response.session ||
210 this.session && this.session != response.session) {
211 this.sessionClosed();
213 this.session = response.session;
214 this.sendRequest(false, request);
216 } else if (request.status == 0) {
217 if (ShellInABox.lastRequestSent + 2000 < Date.now()) {
218 // Timeout, try again
219 this.sendRequest(false, request);
221 this.vt100('\r\n\r\nRequest failed.');
222 this.sessionClosed();
225 this.sessionClosed();
230 ShellInABox.prototype.sendKeys = function(keys) {
231 if (!this.connected) {
234 if (this.keysInFlight || this.session == undefined) {
235 this.pendingKeys += keys;
237 this.keysInFlight = true;
238 keys = this.pendingKeys + keys;
239 this.pendingKeys = '';
240 var request = new XMLHttpRequest();
241 request.open('POST', this.url + '?', true);
242 request.setRequestHeader('Cache-Control', 'no-cache');
243 request.setRequestHeader('Content-Type',
244 'application/x-www-form-urlencoded; charset=utf-8');
245 var content = 'width=' + this.terminalWidth +
246 '&height=' + this.terminalHeight +
247 '&session=' +encodeURIComponent(this.session)+
248 '&keys=' + encodeURIComponent(keys);
249 request.onreadystatechange = function(shellInABox) {
252 return shellInABox.keyPressReadyStateChange(request);
257 request.send(content);
261 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
262 if (request.readyState == 4 /* XHR_LOADED */) {
263 this.keysInFlight = false;
264 if (this.pendingKeys) {
270 ShellInABox.prototype.keysPressed = function(ch) {
271 var hex = '0123456789ABCDEF';
273 for (var i = 0; i < ch.length; i++) {
274 var c = ch.charCodeAt(i);
276 s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
277 } else if (c < 0x800) {
278 s += hex.charAt(0xC + (c >> 10) ) +
279 hex.charAt( (c >> 6) & 0xF ) +
280 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
281 hex.charAt( c & 0xF );
282 } else if (c < 0x10000) {
284 hex.charAt( (c >> 12) ) +
285 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
286 hex.charAt( (c >> 6) & 0xF ) +
287 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
288 hex.charAt( c & 0xF );
289 } else if (c < 0x110000) {
291 hex.charAt( (c >> 18) ) +
292 hex.charAt(0x8 + ((c >> 16) & 0x3)) +
293 hex.charAt( (c >> 12) & 0xF ) +
294 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
295 hex.charAt( (c >> 6) & 0xF ) +
296 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
297 hex.charAt( c & 0xF );
303 ShellInABox.prototype.resized = function(w, h) {
304 // Do not send a resize request until we are fully initialized.
306 // sendKeys() always transmits the current terminal size. So, flush all
312 ShellInABox.prototype.toggleSSL = function() {
313 if (document.location.hash != '') {
314 if (this.nextUrl.match(/\?plain$/)) {
315 this.nextUrl = this.nextUrl.replace(/\?plain$/, '');
317 this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain';
320 parent.location = this.nextUrl;
323 this.nextUrl = this.nextUrl.match(/^https:/)
324 ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
325 : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
327 if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
330 if (this.session && this.nextUrl != this.url) {
331 alert('This change will take effect the next time you login.');
335 ShellInABox.prototype.extendContextMenu = function(entries, actions) {
336 // Modify the entries and actions in place, adding any locally defined
338 var oldActions = [ ];
339 for (var i = 0; i < actions.length; i++) {
340 oldActions[i] = actions[i];
342 for (var node = entries.firstChild, i = 0, j = 0; node;
343 node = node.nextSibling) {
344 if (node.tagName == 'LI') {
345 actions[i++] = oldActions[j++];
346 if (node.id == "endconfig") {
348 if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
349 !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
350 // If the server supports both SSL and plain text connections,
351 // provide a menu entry to switch between the two.
352 var newNode = document.createElement('li');
354 if (document.location.hash != '') {
355 isSecure = !this.nextUrl.match(/\?plain$/);
357 isSecure = this.nextUrl.match(/^https:/);
359 newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
360 if (node.nextSibling) {
361 entries.insertBefore(newNode, node.nextSibling);
363 entries.appendChild(newNode);
365 actions[i++] = this.toggleSSL;
368 node.id = 'endconfig';
375 ShellInABox.prototype.about = function() {
376 alert("Shell In A Box version " + "2.10 (revision 239)" +
377 "\nCopyright 2008-2010 by Markus Gutschke\n" +
378 "For more information check http://shellinabox.com" +
379 (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
381 "This product includes software developed by the OpenSSL Project\n" +
382 "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" +
384 "This product includes cryptographic software written by " +
385 "Eric Young\n(eay@cryptsoft.com)" :
390 // VT100.js -- JavaScript based terminal emulator
391 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
393 // This program is free software; you can redistribute it and/or modify
394 // it under the terms of the GNU General Public License version 2 as
395 // published by the Free Software Foundation.
397 // This program is distributed in the hope that it will be useful,
398 // but WITHOUT ANY WARRANTY; without even the implied warranty of
399 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
400 // GNU General Public License for more details.
402 // You should have received a copy of the GNU General Public License along
403 // with this program; if not, write to the Free Software Foundation, Inc.,
404 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
406 // In addition to these license terms, the author grants the following
407 // additional rights:
409 // If you modify this program, or any covered work, by linking or
410 // combining it with the OpenSSL project's OpenSSL library (or a
411 // modified version of that library), containing parts covered by the
412 // terms of the OpenSSL or SSLeay licenses, the author
413 // grants you additional permission to convey the resulting work.
414 // Corresponding Source for a non-source form of such a combination
415 // shall include the source code for the parts of OpenSSL used as well
416 // as that of the covered work.
418 // You may at your option choose to remove this additional permission from
419 // the work, or from any part of it.
421 // It is possible to build this program in a way that it loads OpenSSL
422 // libraries at run-time. If doing so, the following notices are required
423 // by the OpenSSL and SSLeay licenses:
425 // This product includes software developed by the OpenSSL Project
426 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
428 // This product includes cryptographic software written by Eric Young
429 // (eay@cryptsoft.com)
432 // The most up-to-date version of this program is always available from
433 // http://shellinabox.com
438 // The author believes that for the purposes of this license, you meet the
439 // requirements for publishing the source code, if your web server publishes
440 // the source in unmodified form (i.e. with licensing information, comments,
441 // formatting, and identifier names intact). If there are technical reasons
442 // that require you to make changes to the source code when serving the
443 // JavaScript (e.g to remove pre-processor directives from the source), these
444 // changes should be done in a reversible fashion.
446 // The author does not consider websites that reference this script in
447 // unmodified form, and web servers that serve this script in unmodified form
448 // to be derived works. As such, they are believed to be outside of the
449 // scope of this license and not subject to the rights or restrictions of the
450 // GNU General Public License.
452 // If in doubt, consult a legal professional familiar with the laws that
453 // apply in your country.
455 // #define ESnormal 0
457 // #define ESsquare 2
458 // #define ESgetpars 3
459 // #define ESgotpars 4
460 // #define ESdeviceattr 5
461 // #define ESfunckey 6
465 // #define ESsetG2 10
466 // #define ESsetG3 11
468 // #define ESpercent 13
469 // #define ESignore 14
470 // #define ESnonstd 15
471 // #define ESpalette 16
472 // #define EStitle 17
476 // #define ATTR_DEFAULT 0x00F0
477 // #define ATTR_REVERSE 0x0100
478 // #define ATTR_UNDERLINE 0x0200
479 // #define ATTR_DIM 0x0400
480 // #define ATTR_BRIGHT 0x0800
481 // #define ATTR_BLINK 0x1000
483 // #define MOUSE_DOWN 0
484 // #define MOUSE_UP 1
485 // #define MOUSE_CLICK 2
487 function VT100(container) {
488 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
491 this.urlRE = new RegExp(
492 // Known URL protocol are "http", "https", and "ftp".
493 '(?:http|https|ftp)://' +
495 // Optionally allow username and passwords.
496 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
499 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
500 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
501 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
504 '(?::[1-9][0-9]*)?' +
507 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
509 (linkifyURLs <= 1 ? '' :
510 // Also support URLs without a protocol (assume "http").
511 // Optional username and password.
512 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
514 // Hostnames must end with a well-known top-level domain or must be
516 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
519 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
520 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
521 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
522 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
523 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
524 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
525 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
526 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
527 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
528 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
529 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
530 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
531 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
532 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
535 '(?::[1-9][0-9]{0,4})?' +
538 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
540 // In addition, support e-mail address. Optionally, recognize "mailto:"
541 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
544 '[-_.+a-zA-Z0-9]+@' +
547 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
548 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
549 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
550 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
551 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
552 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
553 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
554 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
555 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
556 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
557 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
558 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
559 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
560 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
562 // Optional arguments
563 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
565 this.getUserSettings();
566 this.initializeElements(container);
567 this.maxScrollbackLines = 500;
570 this.isQuestionMark = false;
573 this.savedAttr = [ ];
574 this.savedUseGMap = 0;
575 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
576 this.CodePage437Map, this.DirectToFontMap ];
577 this.savedValid = [ ];
578 this.respondString = '';
579 this.titleString = '';
580 this.internalClipboard = undefined;
584 VT100.prototype.reset = function(clearHistory) {
585 this.isEsc = 0 /* ESnormal */;
586 this.needWrap = false;
587 this.autoWrapMode = true;
588 this.dispCtrl = false;
589 this.toggleMeta = false;
590 this.insertMode = false;
591 this.applKeyMode = false;
592 this.cursorKeyMode = false;
593 this.crLfMode = false;
594 this.offsetMode = false;
595 this.mouseReporting = false;
596 this.printing = false;
597 if (typeof this.printWin != 'undefined' &&
598 this.printWin && !this.printWin.closed) {
599 this.printWin.close();
601 this.printWin = null;
602 this.utfEnabled = this.utfPreferred;
605 this.color = 'ansi0 bgAnsi15';
607 this.attr = 0x00F0 /* ATTR_DEFAULT */;
609 this.GMap = [ this.Latin1Map,
610 this.VT100GraphicsMap,
612 this.DirectToFontMap];
613 this.translate = this.GMap[this.useGMap];
615 this.bottom = this.terminalHeight;
616 this.lastCharacter = ' ';
617 this.userTabStop = [ ];
620 for (var i = 0; i < 2; i++) {
621 while (this.console[i].firstChild) {
622 this.console[i].removeChild(this.console[i].firstChild);
627 this.enableAlternateScreen(false);
629 var wasCompressed = false;
630 var transform = this.getTransformName();
632 for (var i = 0; i < 2; ++i) {
633 wasCompressed |= this.console[i].style[transform] != '';
634 this.console[i].style[transform] = '';
636 this.cursor.style[transform] = '';
637 this.space.style[transform] = '';
638 if (transform == 'filter') {
639 this.console[this.currentScreen].style.width = '';
649 this.isInverted = false;
650 this.refreshInvertedState();
651 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
652 this.color, this.style);
655 VT100.prototype.addListener = function(elem, event, listener) {
657 if (elem.addEventListener) {
658 elem.addEventListener(event, listener, false);
660 elem.attachEvent('on' + event, listener);
666 VT100.prototype.getUserSettings = function() {
667 // Compute hash signature to identify the entries in the userCSS menu.
668 // If the menu is unchanged from last time, default values can be
669 // looked up in a cookie associated with this page.
671 this.utfPreferred = true;
672 this.visualBell = typeof suppressAllAudio != 'undefined' &&
674 this.autoprint = true;
675 this.softKeyboard = false;
676 this.blinkingCursor = true;
677 if (this.visualBell) {
678 this.signature = Math.floor(16807*this.signature + 1) %
681 if (typeof userCSSList != 'undefined') {
682 for (var i = 0; i < userCSSList.length; ++i) {
683 var label = userCSSList[i][0];
684 for (var j = 0; j < label.length; ++j) {
685 this.signature = Math.floor(16807*this.signature+
686 label.charCodeAt(j)) %
689 if (userCSSList[i][1]) {
690 this.signature = Math.floor(16807*this.signature + 1) %
696 var key = 'shellInABox=' + this.signature + ':';
697 var settings = document.cookie.indexOf(key);
699 settings = document.cookie.substr(settings + key.length).
700 replace(/([0-1]*).*/, "$1");
701 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
702 0 : userCSSList.length)) {
703 this.utfPreferred = settings.charAt(0) != '0';
704 this.visualBell = settings.charAt(1) != '0';
705 this.autoprint = settings.charAt(2) != '0';
706 this.softKeyboard = settings.charAt(3) != '0';
707 this.blinkingCursor = settings.charAt(4) != '0';
708 if (typeof userCSSList != 'undefined') {
709 for (var i = 0; i < userCSSList.length; ++i) {
710 userCSSList[i][2] = settings.charAt(i + 5) != '0';
715 this.utfEnabled = this.utfPreferred;
718 VT100.prototype.storeUserSettings = function() {
719 var settings = 'shellInABox=' + this.signature + ':' +
720 (this.utfEnabled ? '1' : '0') +
721 (this.visualBell ? '1' : '0') +
722 (this.autoprint ? '1' : '0') +
723 (this.softKeyboard ? '1' : '0') +
724 (this.blinkingCursor ? '1' : '0');
725 if (typeof userCSSList != 'undefined') {
726 for (var i = 0; i < userCSSList.length; ++i) {
727 settings += userCSSList[i][2] ? '1' : '0';
731 d.setDate(d.getDate() + 3653);
732 document.cookie = settings + ';expires=' + d.toGMTString();
735 VT100.prototype.initializeUserCSSStyles = function() {
736 this.usercssActions = [];
737 if (typeof userCSSList != 'undefined') {
740 var wasSingleSel = 1;
741 var beginOfGroup = 0;
742 for (var i = 0; i <= userCSSList.length; ++i) {
743 if (i < userCSSList.length) {
744 var label = userCSSList[i][0];
745 var newGroup = userCSSList[i][1];
746 var enabled = userCSSList[i][2];
748 // Add user style sheet to document
749 var style = document.createElement('link');
750 var id = document.createAttribute('id');
751 id.nodeValue = 'usercss-' + i;
752 style.setAttributeNode(id);
753 var rel = document.createAttribute('rel');
754 rel.nodeValue = 'stylesheet';
755 style.setAttributeNode(rel);
756 var href = document.createAttribute('href');
757 href.nodeValue = 'usercss-' + i + '.css';
758 style.setAttributeNode(href);
759 var type = document.createAttribute('type');
760 type.nodeValue = 'text/css';
761 style.setAttributeNode(type);
762 document.getElementsByTagName('head')[0].appendChild(style);
763 style.disabled = !enabled;
767 if (newGroup || i == userCSSList.length) {
768 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
769 // The last group had multiple entries that are mutually exclusive;
770 // or the previous to last group did. In either case, we need to
771 // append a "<hr />" before we can add the last group to the menu.
774 wasSingleSel = i - beginOfGroup < 1;
778 for (var j = beginOfGroup; j < i; ++j) {
779 this.usercssActions[this.usercssActions.length] =
780 function(vt100, current, begin, count) {
782 // Deselect all other entries in the group, then either select
783 // (for multiple entries in group) or toggle (for on/off entry)
784 // the current entry.
786 var entry = vt100.getChildById(vt100.menu,
790 for (var c = count; c > 0; ++j) {
791 if (entry.tagName == 'LI') {
794 var label = vt100.usercss.childNodes[j];
796 // Restore label to just the text content
797 if (typeof label.textContent == 'undefined') {
798 var s = label.innerText;
799 label.innerHTML = '';
800 label.appendChild(document.createTextNode(s));
802 label.textContent= label.textContent;
805 // User style sheets are numbered sequentially
806 var sheet = document.getElementById(
810 sheet.disabled = !sheet.disabled;
812 sheet.disabled = false;
814 if (!sheet.disabled) {
815 label.innerHTML= '<img src="/webshell/enabled.gif" />' +
819 sheet.disabled = true;
821 userCSSList[i][2] = !sheet.disabled;
824 entry = entry.nextSibling;
827 // If the font size changed, adjust cursor and line dimensions
828 this.cursor.style.cssText= '';
829 this.cursorWidth = this.cursor.clientWidth;
830 this.cursorHeight = this.lineheight.clientHeight;
831 for (i = 0; i < this.console.length; ++i) {
832 for (var line = this.console[i].firstChild; line;
833 line = line.nextSibling) {
834 line.style.height = this.cursorHeight + 'px';
839 }(this, j, beginOfGroup, i - beginOfGroup);
842 if (i == userCSSList.length) {
848 // Collect all entries in a group, before attaching them to the menu.
849 // This is necessary as we don't know whether this is a group of
850 // mutually exclusive options (which should be separated by "<hr />" on
851 // both ends), or whether this is a on/off toggle, which can be grouped
852 // together with other on/off options.
854 '<li>' + (enabled ? '<img src="/webshell/enabled.gif" />' : '') +
858 this.usercss.innerHTML = menu;
862 VT100.prototype.resetLastSelectedKey = function(e) {
863 var key = this.lastSelectedKey;
868 var position = this.mousePosition(e);
870 // We don't get all the necessary events to reliably reselect a key
871 // if we moved away from it and then back onto it. We approximate the
872 // behavior by remembering the key until either we release the mouse
873 // button (we might never get this event if the mouse has since left
874 // the window), or until we move away too far.
875 var box = this.keyboard.firstChild;
876 if (position[0] < box.offsetLeft + key.offsetWidth ||
877 position[1] < box.offsetTop + key.offsetHeight ||
878 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
879 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
880 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
881 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
882 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
883 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
884 if (this.lastSelectedKey.className) log.console('reset: deselecting');
885 this.lastSelectedKey.className = '';
886 this.lastSelectedKey = undefined;
891 VT100.prototype.showShiftState = function(state) {
892 var style = document.getElementById('shift_state');
894 this.setTextContentRaw(style,
895 '#vt100 #keyboard .shifted {' +
896 'display: inline }' +
897 '#vt100 #keyboard .unshifted {' +
900 this.setTextContentRaw(style, '');
902 var elems = this.keyboard.getElementsByTagName('I');
903 for (var i = 0; i < elems.length; ++i) {
904 if (elems[i].id == '16') {
905 elems[i].className = state ? 'selected' : '';
910 VT100.prototype.showCtrlState = function(state) {
911 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
913 ctrl.className = state ? 'selected' : '';
917 VT100.prototype.showAltState = function(state) {
918 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
920 alt.className = state ? 'selected' : '';
924 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
929 fake.shiftKey = shift;
932 return this.handleKey(fake);
935 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
936 if (elem == undefined) {
939 if (ch == '\u00A0') {
940 // should be treated as a regular space character.
943 if (ch != undefined && CH == undefined) {
944 // For letter keys, we automatically compute the uppercase character code
945 // from the lowercase one.
946 CH = ch.toUpperCase();
948 if (KEY == undefined && key != undefined) {
949 // Most keys have identically key codes for both lowercase and uppercase
950 // keypresses. Normally, only function keys would have distinct key codes,
951 // whereas regular keys have character codes.
953 } else if (KEY == undefined && CH != undefined) {
954 // For regular keys, copy the character code to the key code.
955 KEY = CH.charCodeAt(0);
957 if (key == undefined && ch != undefined) {
958 // For regular keys, copy the character code to the key code.
959 key = ch.charCodeAt(0);
961 // Convert characters to numeric character codes. If the character code
962 // is undefined (i.e. this is a function key), set it to zero.
963 ch = ch ? ch.charCodeAt(0) : 0;
964 CH = CH ? CH.charCodeAt(0) : 0;
966 // Mouse down events high light the key. We also set lastSelectedKey. This
967 // is needed to that mouseout/mouseover can keep track of the key that
968 // is currently being clicked.
969 this.addListener(elem, 'mousedown',
970 function(vt100, elem, key) { return function(e) {
971 if ((e.which || e.button) == 1) {
972 if (vt100.lastSelectedKey) {
973 vt100.lastSelectedKey.className= '';
975 // Highlight the key while the mouse button is held down.
976 if (key == 16 /* Shift */) {
977 if (!elem.className != vt100.isShift) {
978 vt100.showShiftState(!vt100.isShift);
980 } else if (key == 17 /* Ctrl */) {
981 if (!elem.className != vt100.isCtrl) {
982 vt100.showCtrlState(!vt100.isCtrl);
984 } else if (key == 18 /* Alt */) {
985 if (!elem.className != vt100.isAlt) {
986 vt100.showAltState(!vt100.isAlt);
989 elem.className = 'selected';
991 vt100.lastSelectedKey = elem;
993 return false; }; }(this, elem, key));
995 // Modifier keys update the state of the keyboard, but do not generate
996 // any key clicks that get forwarded to the application.
997 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
998 function(vt100, elem) { return function(e) {
999 if (elem == vt100.lastSelectedKey) {
1000 if (key == 16 /* Shift */) {
1001 // The user clicked the Shift key
1002 vt100.isShift = !vt100.isShift;
1003 vt100.showShiftState(vt100.isShift);
1004 } else if (key == 17 /* Ctrl */) {
1005 vt100.isCtrl = !vt100.isCtrl;
1006 vt100.showCtrlState(vt100.isCtrl);
1007 } else if (key == 18 /* Alt */) {
1008 vt100.isAlt = !vt100.isAlt;
1009 vt100.showAltState(vt100.isAlt);
1011 vt100.lastSelectedKey = undefined;
1013 if (vt100.lastSelectedKey) {
1014 vt100.lastSelectedKey.className = '';
1015 vt100.lastSelectedKey = undefined;
1017 return false; }; }(this, elem) :
1018 // Regular keys generate key clicks, when the mouse button is released or
1019 // when a mouse click event is received.
1020 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
1021 if (vt100.lastSelectedKey) {
1022 if (elem == vt100.lastSelectedKey) {
1023 // The user clicked a key.
1024 if (vt100.isShift) {
1025 vt100.clickedKeyboard(e, elem, CH, KEY,
1026 true, vt100.isCtrl, vt100.isAlt);
1028 vt100.clickedKeyboard(e, elem, ch, key,
1029 false, vt100.isCtrl, vt100.isAlt);
1031 vt100.isShift = false;
1032 vt100.showShiftState(false);
1033 vt100.isCtrl = false;
1034 vt100.showCtrlState(false);
1035 vt100.isAlt = false;
1036 vt100.showAltState(false);
1038 vt100.lastSelectedKey.className = '';
1039 vt100.lastSelectedKey = undefined;
1041 elem.className = '';
1042 return false; }; }(this, elem, ch, key, CH, KEY);
1043 this.addListener(elem, 'mouseup', clicked);
1044 this.addListener(elem, 'click', clicked);
1046 // When moving the mouse away from a key, check if any keys need to be
1048 this.addListener(elem, 'mouseout',
1049 function(vt100, elem, key) { return function(e) {
1050 if (key == 16 /* Shift */) {
1051 if (!elem.className == vt100.isShift) {
1052 vt100.showShiftState(vt100.isShift);
1054 } else if (key == 17 /* Ctrl */) {
1055 if (!elem.className == vt100.isCtrl) {
1056 vt100.showCtrlState(vt100.isCtrl);
1058 } else if (key == 18 /* Alt */) {
1059 if (!elem.className == vt100.isAlt) {
1060 vt100.showAltState(vt100.isAlt);
1062 } else if (elem.className) {
1063 elem.className = '';
1064 vt100.lastSelectedKey = elem;
1065 } else if (vt100.lastSelectedKey) {
1066 vt100.resetLastSelectedKey(e);
1068 return false; }; }(this, elem, key));
1070 // When moving the mouse over a key, select it if the user is still holding
1071 // the mouse button down (i.e. elem == lastSelectedKey)
1072 this.addListener(elem, 'mouseover',
1073 function(vt100, elem, key) { return function(e) {
1074 if (elem == vt100.lastSelectedKey) {
1075 if (key == 16 /* Shift */) {
1076 if (!elem.className != vt100.isShift) {
1077 vt100.showShiftState(!vt100.isShift);
1079 } else if (key == 17 /* Ctrl */) {
1080 if (!elem.className != vt100.isCtrl) {
1081 vt100.showCtrlState(!vt100.isCtrl);
1083 } else if (key == 18 /* Alt */) {
1084 if (!elem.className != vt100.isAlt) {
1085 vt100.showAltState(!vt100.isAlt);
1087 } else if (!elem.className) {
1088 elem.className = 'selected';
1091 vt100.resetLastSelectedKey(e);
1093 return false; }; }(this, elem, key));
1096 VT100.prototype.initializeKeyBindings = function(elem) {
1098 if (elem.nodeName == "I" || elem.nodeName == "B") {
1100 // Function keys. The Javascript keycode is part of the "id"
1101 var i = parseInt(elem.id);
1103 // If the id does not parse as a number, it is not a keycode.
1104 this.addKeyBinding(elem, undefined, i);
1107 var child = elem.firstChild;
1109 if (child.nodeName == "#text") {
1110 // If the key only has a text node as a child, then it is a letter.
1111 // Automatically compute the lower and upper case version of the
1113 var text = this.getTextContent(child) ||
1114 this.getTextContent(elem);
1115 this.addKeyBinding(elem, text.toLowerCase());
1116 } else if (child.nextSibling) {
1117 // If the key has two children, they are the lower and upper case
1118 // character code, respectively.
1119 this.addKeyBinding(elem, this.getTextContent(child), undefined,
1120 this.getTextContent(child.nextSibling));
1126 // Recursively parse all other child nodes.
1127 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
1128 this.initializeKeyBindings(elem);
1132 VT100.prototype.initializeKeyboardButton = function() {
1133 // Configure mouse event handlers for button that displays/hides keyboard
1134 this.addListener(this.keyboardImage, 'click',
1135 function(vt100) { return function(e) {
1136 if (vt100.keyboard.style.display != '') {
1137 if (vt100.reconnectBtn.style.visibility != '') {
1138 vt100.initializeKeyboard();
1139 vt100.showSoftKeyboard();
1142 vt100.hideSoftKeyboard();
1143 vt100.input.focus();
1145 return false; }; }(this));
1147 // Enable button that displays keyboard
1148 if (this.softKeyboard) {
1149 this.keyboardImage.style.visibility = 'visible';
1153 VT100.prototype.initializeKeyboard = function() {
1154 // Only need to initialize the keyboard the very first time. When doing so,
1155 // copy the keyboard layout from the iframe.
1156 if (this.keyboard.firstChild) {
1159 this.keyboard.innerHTML =
1160 this.layout.contentDocument.body.innerHTML;
1161 var box = this.keyboard.firstChild;
1162 this.hideSoftKeyboard();
1164 // Configure mouse event handlers for on-screen keyboard
1165 this.addListener(this.keyboard, 'click',
1166 function(vt100) { return function(e) {
1167 vt100.hideSoftKeyboard();
1168 vt100.input.focus();
1169 return false; }; }(this));
1170 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
1171 this.addListener(box, 'click', this.cancelEvent);
1172 this.addListener(box, 'mouseup',
1173 function(vt100) { return function(e) {
1174 if (vt100.lastSelectedKey) {
1175 vt100.lastSelectedKey.className = '';
1176 vt100.lastSelectedKey = undefined;
1178 return false; }; }(this));
1179 this.addListener(box, 'mouseout',
1180 function(vt100) { return function(e) {
1181 return vt100.resetLastSelectedKey(e); }; }(this));
1182 this.addListener(box, 'mouseover',
1183 function(vt100) { return function(e) {
1184 return vt100.resetLastSelectedKey(e); }; }(this));
1186 // Configure SHIFT key behavior
1187 var style = document.createElement('style');
1188 var id = document.createAttribute('id');
1189 id.nodeValue = 'shift_state';
1190 style.setAttributeNode(id);
1191 var type = document.createAttribute('type');
1192 type.nodeValue = 'text/css';
1193 style.setAttributeNode(type);
1194 document.getElementsByTagName('head')[0].appendChild(style);
1196 // Set up key bindings
1197 this.initializeKeyBindings(box);
1200 VT100.prototype.initializeElements = function(container) {
1201 // If the necessary objects have not already been defined in the HTML
1202 // page, create them now.
1204 this.container = container;
1205 } else if (!(this.container = document.getElementById('vt100'))) {
1206 this.container = document.createElement('div');
1207 this.container.id = 'vt100';
1208 document.body.appendChild(this.container);
1211 if (!this.getChildById(this.container, 'reconnect') ||
1212 !this.getChildById(this.container, 'menu') ||
1213 !this.getChildById(this.container, 'keyboard') ||
1214 !this.getChildById(this.container, 'kbd_button') ||
1215 !this.getChildById(this.container, 'kbd_img') ||
1216 !this.getChildById(this.container, 'layout') ||
1217 !this.getChildById(this.container, 'scrollable') ||
1218 !this.getChildById(this.container, 'console') ||
1219 !this.getChildById(this.container, 'alt_console') ||
1220 !this.getChildById(this.container, 'ieprobe') ||
1221 !this.getChildById(this.container, 'padding') ||
1222 !this.getChildById(this.container, 'cursor') ||
1223 !this.getChildById(this.container, 'lineheight') ||
1224 !this.getChildById(this.container, 'usercss') ||
1225 !this.getChildById(this.container, 'space') ||
1226 !this.getChildById(this.container, 'input') ||
1227 !this.getChildById(this.container, 'cliphelper')) {
1228 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
1229 // we might get a pointless warning that a suitable plugin is not yet
1230 // installed. If in doubt, we'd rather just stay silent.
1233 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
1235 embed = typeof suppressAllAudio != 'undefined' &&
1236 suppressAllAudio ? "" :
1237 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
1238 'id="beep_embed" ' +
1240 'autostart="false" ' +
1242 'enablejavascript="true" ' +
1243 'type="audio/x-wav" ' +
1246 'style="position:absolute;left:-1000px;top:-1000px" />';
1251 this.container.innerHTML =
1252 '<div id="reconnect" style="visibility: hidden">' +
1253 '<input type="button" value="Connect" ' +
1254 'onsubmit="return false" />' +
1256 '<div id="cursize" style="visibility: hidden">' +
1258 '<div id="menu"></div>' +
1259 '<div id="keyboard" unselectable="on">' +
1261 '<div id="scrollable">' +
1262 '<table id="kbd_button">' +
1263 '<tr><td width="100%"> </td>' +
1264 '<td><img id="kbd_img" src="/webshell/keyboard.png" /></td>' +
1265 '<td> </td></tr>' +
1267 '<pre id="lineheight"> </pre>' +
1268 '<pre id="console">' +
1270 '<div id="ieprobe"><span> </span></div>' +
1272 '<pre id="alt_console" style="display: none"></pre>' +
1273 '<div id="padding"></div>' +
1274 '<pre id="cursor"> </pre>' +
1276 '<div class="hidden">' +
1277 '<div id="usercss"></div>' +
1278 '<pre><div><span id="space"></span></div></pre>' +
1279 '<input type="textfield" id="input" autocorrect="off" autocapitalize="off" />' +
1280 '<input type="textfield" id="cliphelper" />' +
1281 (typeof suppressAllAudio != 'undefined' &&
1282 suppressAllAudio ? "" :
1283 embed + '<bgsound id="beep_bgsound" loop=1 />') +
1284 '<iframe id="layout" src="/webshell/keyboard.html" />' +
1288 // Find the object used for playing the "beep" sound, if any.
1289 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
1290 this.beeper = undefined;
1292 this.beeper = this.getChildById(this.container,
1294 if (!this.beeper || !this.beeper.Play) {
1295 this.beeper = this.getChildById(this.container,
1297 if (!this.beeper || typeof this.beeper.src == 'undefined') {
1298 this.beeper = undefined;
1303 // Initialize the variables for finding the text console and the
1305 this.reconnectBtn = this.getChildById(this.container,'reconnect');
1306 this.curSizeBox = this.getChildById(this.container, 'cursize');
1307 this.menu = this.getChildById(this.container, 'menu');
1308 this.keyboard = this.getChildById(this.container, 'keyboard');
1309 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
1310 this.layout = this.getChildById(this.container, 'layout');
1311 this.scrollable = this.getChildById(this.container,
1313 this.lineheight = this.getChildById(this.container,
1316 [ this.getChildById(this.container, 'console'),
1317 this.getChildById(this.container, 'alt_console') ];
1318 var ieProbe = this.getChildById(this.container, 'ieprobe');
1319 this.padding = this.getChildById(this.container, 'padding');
1320 this.cursor = this.getChildById(this.container, 'cursor');
1321 this.usercss = this.getChildById(this.container, 'usercss');
1322 this.space = this.getChildById(this.container, 'space');
1323 this.input = this.getChildById(this.container, 'input');
1324 this.cliphelper = this.getChildById(this.container,
1327 // Add any user selectable style sheets to the menu
1328 this.initializeUserCSSStyles();
1330 // Remember the dimensions of a standard character glyph. We would
1331 // expect that we could just check cursor.clientWidth/Height at any time,
1332 // but it turns out that browsers sometimes invalidate these values
1333 // (e.g. while displaying a print preview screen).
1334 this.cursorWidth = this.cursor.clientWidth;
1335 this.cursorHeight = this.lineheight.clientHeight;
1337 // IE has a slightly different boxing model, that we need to compensate for
1338 this.isIE = ieProbe.offsetTop > 1;
1339 ieProbe = undefined;
1340 this.console.innerHTML = '';
1342 // Determine if the terminal window is positioned at the beginning of the
1343 // page, or if it is embedded somewhere else in the page. For full-screen
1344 // terminals, automatically resize whenever the browser window changes.
1345 var marginTop = parseInt(this.getCurrentComputedStyle(
1346 document.body, 'marginTop'));
1347 var marginLeft = parseInt(this.getCurrentComputedStyle(
1348 document.body, 'marginLeft'));
1349 var marginRight = parseInt(this.getCurrentComputedStyle(
1350 document.body, 'marginRight'));
1351 var x = this.container.offsetLeft;
1352 var y = this.container.offsetTop;
1353 for (var parent = this.container; parent = parent.offsetParent; ) {
1354 x += parent.offsetLeft;
1355 y += parent.offsetTop;
1357 this.isEmbedded = marginTop != y ||
1359 (window.innerWidth ||
1360 document.documentElement.clientWidth ||
1361 document.body.clientWidth) -
1362 marginRight != x + this.container.offsetWidth;
1363 if (!this.isEmbedded) {
1364 // Some browsers generate resize events when the terminal is first
1365 // shown. Disable showing the size indicator until a little bit after
1366 // the terminal has been rendered the first time.
1367 this.indicateSize = false;
1368 setTimeout(function(vt100) {
1370 vt100.indicateSize = true;
1373 this.addListener(window, 'resize',
1376 vt100.hideContextMenu();
1378 vt100.showCurrentSize();
1382 // Hide extra scrollbars attached to window
1383 document.body.style.margin = '0px';
1384 try { document.body.style.overflow ='hidden'; } catch (e) { }
1385 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1388 // Set up onscreen soft keyboard
1389 this.initializeKeyboardButton();
1391 // Hide context menu
1392 this.hideContextMenu();
1394 // Add listener to reconnect button
1395 this.addListener(this.reconnectBtn.firstChild, 'click',
1398 var rc = vt100.reconnect();
1399 vt100.input.focus();
1404 // Add input listeners
1405 this.addListener(this.input, 'blur',
1407 return function() { vt100.blurCursor(); } }(this));
1408 this.addListener(this.input, 'focus',
1410 return function() { vt100.focusCursor(); } }(this));
1411 this.addListener(this.input, 'keydown',
1413 return function(e) {
1414 if (!e) e = window.event;
1415 return vt100.keyDown(e); } }(this));
1416 this.addListener(this.input, 'keypress',
1418 return function(e) {
1419 if (!e) e = window.event;
1420 return vt100.keyPressed(e); } }(this));
1421 this.addListener(this.input, 'keyup',
1423 return function(e) {
1424 if (!e) e = window.event;
1425 return vt100.keyUp(e); } }(this));
1427 // Attach listeners that move the focus to the <input> field. This way we
1428 // can make sure that we can receive keyboard input.
1429 var mouseEvent = function(vt100, type) {
1430 return function(e) {
1431 if (!e) e = window.event;
1432 return vt100.mouseEvent(e, type);
1435 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1436 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
1437 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
1439 // Check that browser supports drag and drop
1440 if ('draggable' in document.createElement('span')) {
1441 var dropEvent = function (vt100) {
1442 return function(e) {
1443 if (!e) e = window.event;
1444 if (e.preventDefault) e.preventDefault();
1445 vt100.keysPressed(e.dataTransfer.getData('Text'));
1449 // Tell the browser that we *can* drop on this target
1450 this.addListener(this.scrollable, 'dragover', cancel);
1451 this.addListener(this.scrollable, 'dragenter', cancel);
1453 // Add a listener for the drop event
1454 this.addListener(this.scrollable, 'drop', dropEvent(this));
1457 // Initialize the blank terminal window.
1458 this.currentScreen = 0;
1461 this.numScrollbackLines = 0;
1463 this.bottom = 0x7FFFFFFF;
1470 function cancel(event) {
1471 if (event.preventDefault) {
1472 event.preventDefault();
1477 VT100.prototype.getChildById = function(parent, id) {
1478 var nodeList = parent.all || parent.getElementsByTagName('*');
1479 if (typeof nodeList.namedItem == 'undefined') {
1480 for (var i = 0; i < nodeList.length; i++) {
1481 if (nodeList[i].id == id) {
1487 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1488 return elem ? elem[0] || elem : null;
1492 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1493 if (typeof elem.currentStyle != 'undefined') {
1494 return elem.currentStyle[style];
1496 return document.defaultView.getComputedStyle(elem, null)[style];
1500 VT100.prototype.reconnect = function() {
1504 VT100.prototype.showReconnect = function(state) {
1506 this.hideSoftKeyboard();
1507 this.reconnectBtn.style.visibility = '';
1509 this.reconnectBtn.style.visibility = 'hidden';
1513 VT100.prototype.repairElements = function(console) {
1514 for (var line = console.firstChild; line; line = line.nextSibling) {
1515 if (!line.clientHeight) {
1516 var newLine = document.createElement(line.tagName);
1517 newLine.style.cssText = line.style.cssText;
1518 newLine.className = line.className;
1519 if (line.tagName == 'DIV') {
1520 for (var span = line.firstChild; span; span = span.nextSibling) {
1521 var newSpan = document.createElement(span.tagName);
1522 newSpan.style.cssText = span.style.cssText;
1523 newSpan.className = span.className;
1524 this.setTextContent(newSpan, this.getTextContent(span));
1525 newLine.appendChild(newSpan);
1528 this.setTextContent(newLine, this.getTextContent(line));
1530 line.parentNode.replaceChild(newLine, line);
1536 VT100.prototype.resized = function(w, h) {
1539 VT100.prototype.resizer = function() {
1540 // Hide onscreen soft keyboard
1541 this.hideSoftKeyboard();
1543 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1544 // Recreating it, will repair it.
1545 var newCursor = document.createElement('pre');
1546 this.setTextContent(newCursor, ' ');
1547 newCursor.id = 'cursor';
1548 newCursor.style.cssText = this.cursor.style.cssText;
1549 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1550 if (!newCursor.clientHeight) {
1551 // Things are broken right now. This is probably because we are
1552 // displaying the print-preview. Just don't change any of our settings
1553 // until the print dialog is closed again.
1554 newCursor.parentNode.removeChild(newCursor);
1557 // Swap the old broken cursor for the newly created one.
1558 this.cursor.parentNode.removeChild(this.cursor);
1559 this.cursor = newCursor;
1562 // Really horrible things happen if the contents of the terminal changes
1563 // while the print-preview is showing. We get HTML elements that show up
1564 // in the DOM, but that do not take up any space. Find these elements and
1566 this.repairElements(this.console[0]);
1567 this.repairElements(this.console[1]);
1569 // Lock the cursor size to the size of a normal character. This helps with
1570 // characters that are taller/shorter than normal. Unfortunately, we will
1571 // still get confused if somebody enters a character that is wider/narrower
1572 // than normal. This can happen if the browser tries to substitute a
1573 // characters from a different font.
1574 this.cursor.style.width = this.cursorWidth + 'px';
1575 this.cursor.style.height = this.cursorHeight + 'px';
1577 // Adjust height for one pixel padding of the #vt100 element.
1578 // The latter is necessary to properly display the inactive cursor.
1579 var console = this.console[this.currentScreen];
1580 var height = (this.isEmbedded ? this.container.clientHeight
1581 : (window.innerHeight ||
1582 document.documentElement.clientHeight ||
1583 document.body.clientHeight))-1;
1584 var partial = height % this.cursorHeight;
1585 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1586 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1587 var oldTerminalHeight = this.terminalHeight;
1589 this.updateHeight();
1591 // Clip the cursor to the visible screen.
1592 var cx = this.cursorX;
1593 var cy = this.cursorY + this.numScrollbackLines;
1595 // The alternate screen never keeps a scroll back buffer.
1596 this.updateNumScrollbackLines();
1597 while (this.currentScreen && this.numScrollbackLines > 0) {
1598 console.removeChild(console.firstChild);
1599 this.numScrollbackLines--;
1601 cy -= this.numScrollbackLines;
1604 } else if (cx > this.terminalWidth) {
1605 cx = this.terminalWidth - 1;
1612 } else if (cy > this.terminalHeight) {
1613 cy = this.terminalHeight - 1;
1619 // Clip the scroll region to the visible screen.
1620 if (this.bottom > this.terminalHeight ||
1621 this.bottom == oldTerminalHeight) {
1622 this.bottom = this.terminalHeight;
1624 if (this.top >= this.bottom) {
1625 this.top = this.bottom-1;
1631 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1632 // particularly important after changing the screen number), and reset
1633 // the scroll region to the default.
1634 this.truncateLines(this.terminalWidth);
1635 this.putString(cx, cy, '', undefined);
1636 this.scrollable.scrollTop = this.numScrollbackLines *
1637 this.cursorHeight + 1;
1639 // Update classNames for lines in the scrollback buffer
1640 var line = console.firstChild;
1641 for (var i = 0; i < this.numScrollbackLines; i++) {
1642 line.className = 'scrollback';
1643 line = line.nextSibling;
1646 line.className = '';
1647 line = line.nextSibling;
1650 // Reposition the reconnect button
1651 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1653 this.reconnectBtn.clientWidth)/2 + 'px';
1654 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1655 this.reconnectBtn.clientHeight)/2 + 'px';
1657 // Send notification that the window size has been changed
1658 this.resized(this.terminalWidth, this.terminalHeight);
1661 VT100.prototype.showCurrentSize = function() {
1662 if (!this.indicateSize) {
1665 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1666 this.terminalHeight;
1667 this.curSizeBox.style.left =
1668 (this.terminalWidth*this.cursorWidth/
1670 this.curSizeBox.clientWidth)/2 + 'px';
1671 this.curSizeBox.style.top =
1672 (this.terminalHeight*this.cursorHeight -
1673 this.curSizeBox.clientHeight)/2 + 'px';
1674 this.curSizeBox.style.visibility = '';
1675 if (this.curSizeTimeout) {
1676 clearTimeout(this.curSizeTimeout);
1679 // Only show the terminal size for a short amount of time after resizing.
1680 // Then hide this information, again. Some browsers generate resize events
1681 // throughout the entire resize operation. This is nice, and we will show
1682 // the terminal size while the user is dragging the window borders.
1683 // Other browsers only generate a single event when the user releases the
1684 // mouse. In those cases, we can only show the terminal size once at the
1685 // end of the resize operation.
1686 this.curSizeTimeout = setTimeout(function(vt100) {
1688 vt100.curSizeTimeout = null;
1689 vt100.curSizeBox.style.visibility = 'hidden';
1694 VT100.prototype.selection = function() {
1696 return '' + (window.getSelection && window.getSelection() ||
1697 document.selection && document.selection.type == 'Text' &&
1698 document.selection.createRange().text || '');
1704 VT100.prototype.cancelEvent = function(event) {
1706 // For non-IE browsers
1707 event.stopPropagation();
1708 event.preventDefault();
1713 event.cancelBubble = true;
1714 event.returnValue = false;
1722 VT100.prototype.mousePosition = function(event) {
1723 var offsetX = this.container.offsetLeft;
1724 var offsetY = this.container.offsetTop;
1725 for (var e = this.container; e = e.offsetParent; ) {
1726 offsetX += e.offsetLeft;
1727 offsetY += e.offsetTop;
1729 return [ event.clientX - offsetX,
1730 event.clientY - offsetY ];
1733 VT100.prototype.mouseEvent = function(event, type) {
1734 // If any text is currently selected, do not move the focus as that would
1735 // invalidate the selection.
1736 var selection = this.selection();
1737 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1741 // Compute mouse position in characters.
1742 var position = this.mousePosition(event);
1743 var x = Math.floor(position[0] / this.cursorWidth);
1744 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1745 this.cursorHeight) - this.numScrollbackLines;
1747 if (x >= this.terminalWidth) {
1748 x = this.terminalWidth - 1;
1755 if (y >= this.terminalHeight) {
1756 y = this.terminalHeight - 1;
1764 // Compute button number and modifier keys.
1765 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
1766 typeof event.pageX != 'undefined' ? event.button :
1767 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1768 if (button != undefined) {
1769 if (event.shiftKey) {
1772 if (event.altKey || event.metaKey) {
1775 if (event.ctrlKey) {
1780 // Report mouse events if they happen inside of the current screen and
1781 // with the SHIFT key unpressed. Both of these restrictions do not apply
1782 // for button releases, as we always want to report those.
1783 if (this.mouseReporting && !selection.length &&
1784 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1785 if (inside || type != 0 /* MOUSE_DOWN */) {
1786 if (button != undefined) {
1787 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1788 String.fromCharCode(x + 33) +
1789 String.fromCharCode(y + 33);
1790 if (type != 2 /* MOUSE_CLICK */) {
1791 this.keysPressed(report);
1794 // If we reported the event, stop propagating it (not sure, if this
1795 // actually works on most browsers; blocking the global "oncontextmenu"
1796 // even is still necessary).
1797 return this.cancelEvent(event);
1802 // Bring up context menu.
1803 if (button == 2 && !event.shiftKey) {
1804 if (type == 0 /* MOUSE_DOWN */) {
1805 this.showContextMenu(position[0], position[1]);
1807 return this.cancelEvent(event);
1810 if (this.mouseReporting) {
1812 event.shiftKey = false;
1820 VT100.prototype.replaceChar = function(s, ch, repl) {
1821 for (var i = -1;;) {
1822 i = s.indexOf(ch, i + 1);
1826 s = s.substr(0, i) + repl + s.substr(i + 1);
1831 VT100.prototype.htmlEscape = function(s) {
1832 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1833 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1836 VT100.prototype.getTextContent = function(elem) {
1837 return elem.textContent ||
1838 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1841 VT100.prototype.setTextContentRaw = function(elem, s) {
1842 // Updating the content of an element is an expensive operation. It actually
1843 // pays off to first check whether the element is still unchanged.
1844 if (typeof elem.textContent == 'undefined') {
1845 if (elem.innerText != s) {
1849 // Very old versions of IE do not allow setting innerText. Instead,
1850 // remove all children, by setting innerHTML and then set the text
1851 // using DOM methods.
1852 elem.innerHTML = '';
1853 elem.appendChild(document.createTextNode(
1854 this.replaceChar(s, ' ', '\u00A0')));
1858 if (elem.textContent != s) {
1859 elem.textContent = s;
1864 VT100.prototype.setTextContent = function(elem, s) {
1865 // Check if we find any URLs in the text. If so, automatically convert them
1867 if (this.urlRE && this.urlRE.test(s)) {
1871 if (RegExp.leftContext != null) {
1872 inner += this.htmlEscape(RegExp.leftContext);
1873 consumed += RegExp.leftContext.length;
1875 var url = this.htmlEscape(RegExp.lastMatch);
1878 // If no protocol was specified, try to guess a reasonable one.
1879 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1880 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1881 var slash = url.indexOf('/');
1882 var at = url.indexOf('@');
1883 var question = url.indexOf('?');
1885 (at < question || question < 0) &&
1886 (slash < 0 || (question > 0 && slash > question))) {
1887 fullUrl = 'mailto:' + url;
1889 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1894 inner += '<a target="vt100Link" href="' + fullUrl +
1895 '">' + url + '</a>';
1896 consumed += RegExp.lastMatch.length;
1897 s = s.substr(consumed);
1898 if (!this.urlRE.test(s)) {
1899 if (RegExp.rightContext != null) {
1900 inner += this.htmlEscape(RegExp.rightContext);
1905 elem.innerHTML = inner;
1909 this.setTextContentRaw(elem, s);
1912 VT100.prototype.insertBlankLine = function(y, color, style) {
1913 // Insert a blank line a position y. This method ignores the scrollback
1914 // buffer. The caller has to add the length of the scrollback buffer to
1915 // the position, if necessary.
1916 // If the position is larger than the number of current lines, this
1917 // method just adds a new line right after the last existing one. It does
1918 // not add any missing lines in between. It is the caller's responsibility
1921 color = 'ansi0 bgAnsi15';
1927 if (color != 'ansi0 bgAnsi15' && !style) {
1928 line = document.createElement('pre');
1929 this.setTextContent(line, '\n');
1931 line = document.createElement('div');
1932 var span = document.createElement('span');
1933 span.style.cssText = style;
1934 span.className = color;
1935 this.setTextContent(span, this.spaces(this.terminalWidth));
1936 line.appendChild(span);
1938 line.style.height = this.cursorHeight + 'px';
1939 var console = this.console[this.currentScreen];
1940 if (console.childNodes.length > y) {
1941 console.insertBefore(line, console.childNodes[y]);
1943 console.appendChild(line);
1947 VT100.prototype.updateWidth = function() {
1948 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1949 this.cursorWidth*this.scale);
1950 return this.terminalWidth;
1953 VT100.prototype.updateHeight = function() {
1954 // We want to be able to display either a terminal window that fills the
1955 // entire browser window, or a terminal window that is contained in a
1956 // <div> which is embededded somewhere in the web page.
1957 if (this.isEmbedded) {
1958 // Embedded terminal. Use size of the containing <div> (id="vt100").
1959 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1962 // Use the full browser window.
1963 this.terminalHeight = Math.floor(((window.innerHeight ||
1964 document.documentElement.clientHeight ||
1965 document.body.clientHeight)-1)/
1968 return this.terminalHeight;
1971 VT100.prototype.updateNumScrollbackLines = function() {
1972 var scrollback = Math.floor(
1973 this.console[this.currentScreen].offsetHeight /
1974 this.cursorHeight) -
1975 this.terminalHeight;
1976 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1977 return this.numScrollbackLines;
1980 VT100.prototype.truncateLines = function(width) {
1984 for (var line = this.console[this.currentScreen].firstChild; line;
1985 line = line.nextSibling) {
1986 if (line.tagName == 'DIV') {
1989 // Traverse current line and truncate it once we saw "width" characters
1990 for (var span = line.firstChild; span;
1991 span = span.nextSibling) {
1992 var s = this.getTextContent(span);
1994 if (x + l > width) {
1995 this.setTextContent(span, s.substr(0, width - x));
1996 while (span.nextSibling) {
1997 line.removeChild(line.lastChild);
2003 // Prune white space from the end of the current line
2004 var span = line.lastChild;
2006 span.className == 'ansi0 bgAnsi15' &&
2007 !span.style.cssText.length) {
2008 // Scan backwards looking for first non-space character
2009 var s = this.getTextContent(span);
2010 for (var i = s.length; i--; ) {
2011 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2012 if (i+1 != s.length) {
2013 this.setTextContent(s.substr(0, i+1));
2021 span = span.previousSibling;
2023 // Remove blank <span>'s from end of line
2024 line.removeChild(sibling);
2026 // Remove entire line (i.e. <div>), if empty
2027 var blank = document.createElement('pre');
2028 blank.style.height = this.cursorHeight + 'px';
2029 this.setTextContent(blank, '\n');
2030 line.parentNode.replaceChild(blank, line);
2038 VT100.prototype.putString = function(x, y, text, color, style) {
2040 color = 'ansi0 bgAnsi15';
2045 var yIdx = y + this.numScrollbackLines;
2051 var console = this.console[this.currentScreen];
2052 if (!text.length && (yIdx >= console.childNodes.length ||
2053 console.childNodes[yIdx].tagName != 'DIV')) {
2054 // Positioning cursor to a blank location
2057 // Create missing blank lines at end of page
2058 while (console.childNodes.length <= yIdx) {
2059 // In order to simplify lookups, we want to make sure that each line
2060 // is represented by exactly one element (and possibly a whole bunch of
2062 // For non-blank lines, we can create a <div> containing one or more
2063 // <span>s. For blank lines, this fails as browsers tend to optimize them
2064 // away. But fortunately, a <pre> tag containing a newline character
2065 // appears to work for all browsers (a would also work, but then
2066 // copying from the browser window would insert superfluous spaces into
2068 this.insertBlankLine(yIdx);
2070 line = console.childNodes[yIdx];
2072 // If necessary, promote blank '\n' line to a <div> tag
2073 if (line.tagName != 'DIV') {
2074 var div = document.createElement('div');
2075 div.style.height = this.cursorHeight + 'px';
2076 div.innerHTML = '<span></span>';
2077 console.replaceChild(div, line);
2081 // Scan through list of <span>'s until we find the one where our text
2083 span = line.firstChild;
2085 while (span.nextSibling && xPos < x) {
2086 len = this.getTextContent(span).length;
2087 if (xPos + len > x) {
2091 span = span.nextSibling;
2095 // If current <span> is not long enough, pad with spaces or add new
2097 s = this.getTextContent(span);
2098 var oldColor = span.className;
2099 var oldStyle = span.style.cssText;
2100 if (xPos + s.length < x) {
2101 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
2102 span = document.createElement('span');
2103 line.appendChild(span);
2104 span.className = 'ansi0 bgAnsi15';
2105 span.style.cssText = '';
2106 oldColor = 'ansi0 bgAnsi15';
2113 } while (xPos + s.length < x);
2116 // If styles do not match, create a new <span>
2117 var del = text.length - s.length + x - xPos;
2118 if (oldColor != color ||
2119 (oldStyle != style && (oldStyle || style))) {
2121 // Replacing text at beginning of existing <span>
2122 if (text.length >= s.length) {
2123 // New text is equal or longer than existing text
2126 // Insert new <span> before the current one, then remove leading
2127 // part of existing <span>, adjust style of new <span>, and finally
2129 sibling = document.createElement('span');
2130 line.insertBefore(sibling, span);
2131 this.setTextContent(span, s.substr(text.length));
2136 // Replacing text some way into the existing <span>
2137 var remainder = s.substr(x + text.length - xPos);
2138 this.setTextContent(span, s.substr(0, x - xPos));
2140 sibling = document.createElement('span');
2141 if (span.nextSibling) {
2142 line.insertBefore(sibling, span.nextSibling);
2144 if (remainder.length) {
2145 sibling = document.createElement('span');
2146 sibling.className = oldColor;
2147 sibling.style.cssText = oldStyle;
2148 this.setTextContent(sibling, remainder);
2149 line.insertBefore(sibling, span.nextSibling);
2152 line.appendChild(sibling);
2154 if (remainder.length) {
2155 sibling = document.createElement('span');
2156 sibling.className = oldColor;
2157 sibling.style.cssText = oldStyle;
2158 this.setTextContent(sibling, remainder);
2159 line.appendChild(sibling);
2164 span.className = color;
2165 span.style.cssText = style;
2167 // Overwrite (partial) <span> with new text
2168 s = s.substr(0, x - xPos) +
2170 s.substr(x + text.length - xPos);
2172 this.setTextContent(span, s);
2175 // Delete all subsequent <span>'s that have just been overwritten
2176 sibling = span.nextSibling;
2177 while (del > 0 && sibling) {
2178 s = this.getTextContent(sibling);
2181 line.removeChild(sibling);
2183 sibling = span.nextSibling;
2185 this.setTextContent(sibling, s.substr(del));
2190 // Merge <span> with next sibling, if styles are identical
2191 if (sibling && span.className == sibling.className &&
2192 span.style.cssText == sibling.style.cssText) {
2193 this.setTextContent(span,
2194 this.getTextContent(span) +
2195 this.getTextContent(sibling));
2196 line.removeChild(sibling);
2202 this.cursorX = x + text.length;
2203 if (this.cursorX >= this.terminalWidth) {
2204 this.cursorX = this.terminalWidth - 1;
2205 if (this.cursorX < 0) {
2211 if (!this.cursor.style.visibility) {
2212 var idx = this.cursorX - xPos;
2214 // If we are in a non-empty line, take the cursor Y position from the
2215 // other elements in this line. If dealing with broken, non-proportional
2216 // fonts, this is likely to yield better results.
2217 pixelY = span.offsetTop +
2218 span.offsetParent.offsetTop;
2219 s = this.getTextContent(span);
2220 var nxtIdx = idx - s.length;
2222 this.setTextContent(this.cursor, s.charAt(idx));
2223 pixelX = span.offsetLeft +
2224 idx*span.offsetWidth / s.length;
2227 pixelX = span.offsetLeft + span.offsetWidth;
2229 if (span.nextSibling) {
2230 s = this.getTextContent(span.nextSibling);
2231 this.setTextContent(this.cursor, s.charAt(nxtIdx));
2233 pixelX = span.nextSibling.offsetLeft +
2234 nxtIdx*span.offsetWidth / s.length;
2237 this.setTextContent(this.cursor, ' ');
2241 this.setTextContent(this.cursor, ' ');
2245 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
2248 this.setTextContent(this.space, this.spaces(this.cursorX));
2249 this.cursor.style.left = (this.space.offsetWidth +
2250 console.offsetLeft)/this.scale + 'px';
2252 this.cursorY = yIdx - this.numScrollbackLines;
2254 this.cursor.style.top = pixelY + 'px';
2256 this.cursor.style.top = yIdx*this.cursorHeight +
2257 console.offsetTop + 'px';
2261 // Merge <span> with previous sibling, if styles are identical
2262 if ((sibling = span.previousSibling) &&
2263 span.className == sibling.className &&
2264 span.style.cssText == sibling.style.cssText) {
2265 this.setTextContent(span,
2266 this.getTextContent(sibling) +
2267 this.getTextContent(span));
2268 line.removeChild(sibling);
2271 // Prune white space from the end of the current line
2272 span = line.lastChild;
2274 span.className == 'ansi0 bgAnsi15' &&
2275 !span.style.cssText.length) {
2276 // Scan backwards looking for first non-space character
2277 s = this.getTextContent(span);
2278 for (var i = s.length; i--; ) {
2279 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2280 if (i+1 != s.length) {
2281 this.setTextContent(s.substr(0, i+1));
2289 span = span.previousSibling;
2291 // Remove blank <span>'s from end of line
2292 line.removeChild(sibling);
2294 // Remove entire line (i.e. <div>), if empty
2295 var blank = document.createElement('pre');
2296 blank.style.height = this.cursorHeight + 'px';
2297 this.setTextContent(blank, '\n');
2298 line.parentNode.replaceChild(blank, line);
2305 VT100.prototype.gotoXY = function(x, y) {
2306 if (x >= this.terminalWidth) {
2307 x = this.terminalWidth - 1;
2313 if (this.offsetMode) {
2318 maxY = this.terminalHeight;
2326 this.putString(x, y, '', undefined);
2327 this.needWrap = false;
2330 VT100.prototype.gotoXaY = function(x, y) {
2331 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
2334 VT100.prototype.refreshInvertedState = function() {
2335 if (this.isInverted) {
2336 this.scrollable.className += ' inverted';
2338 this.scrollable.className = this.scrollable.className.
2339 replace(/ *inverted/, '');
2343 VT100.prototype.enableAlternateScreen = function(state) {
2344 // Don't do anything, if we are already on the desired screen
2345 if ((state ? 1 : 0) == this.currentScreen) {
2346 // Calling the resizer is not actually necessary. But it is a good way
2347 // of resetting state that might have gotten corrupted.
2352 // We save the full state of the normal screen, when we switch away from it.
2353 // But for the alternate screen, no saving is necessary. We always reset
2354 // it when we switch to it.
2359 // Display new screen, and initialize state (the resizer does that for us).
2360 this.currentScreen = state ? 1 : 0;
2361 this.console[1-this.currentScreen].style.display = 'none';
2362 this.console[this.currentScreen].style.display = '';
2364 // Select appropriate character pitch.
2365 var transform = this.getTransformName();
2368 // Upon enabling the alternate screen, we switch to 80 column mode. But
2369 // upon returning to the regular screen, we restore the mode that was
2370 // in effect previously.
2371 this.console[1].style[transform] = '';
2374 this.console[this.currentScreen].style[transform];
2375 this.cursor.style[transform] = style;
2376 this.space.style[transform] = style;
2377 this.scale = style == '' ? 1.0:1.65;
2378 if (transform == 'filter') {
2379 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
2384 // If we switched to the alternate screen, reset it completely. Otherwise,
2385 // restore the saved state.
2388 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2390 this.restoreCursor();
2394 VT100.prototype.hideCursor = function() {
2395 var hidden = this.cursor.style.visibility == 'hidden';
2397 this.cursor.style.visibility = 'hidden';
2403 VT100.prototype.showCursor = function(x, y) {
2404 if (this.cursor.style.visibility) {
2405 this.cursor.style.visibility = '';
2406 this.putString(x == undefined ? this.cursorX : x,
2407 y == undefined ? this.cursorY : y,
2414 VT100.prototype.scrollBack = function() {
2415 var i = this.scrollable.scrollTop -
2416 this.scrollable.clientHeight;
2417 this.scrollable.scrollTop = i < 0 ? 0 : i;
2420 VT100.prototype.scrollFore = function() {
2421 var i = this.scrollable.scrollTop +
2422 this.scrollable.clientHeight;
2423 this.scrollable.scrollTop = i > this.numScrollbackLines *
2424 this.cursorHeight + 1
2425 ? this.numScrollbackLines *
2426 this.cursorHeight + 1
2430 VT100.prototype.spaces = function(i) {
2438 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2443 if (w > this.terminalWidth) {
2444 w = this.terminalWidth;
2446 if ((w -= x) <= 0) {
2453 if (h > this.terminalHeight) {
2454 h = this.terminalHeight;
2456 if ((h -= y) <= 0) {
2460 // Special case the situation where we clear the entire screen, and we do
2461 // not have a scrollback buffer. In that case, we should just remove all
2463 if (!this.numScrollbackLines &&
2464 w == this.terminalWidth && h == this.terminalHeight &&
2465 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2466 var console = this.console[this.currentScreen];
2467 while (console.lastChild) {
2468 console.removeChild(console.lastChild);
2470 this.putString(this.cursorX, this.cursorY, '', undefined);
2472 var hidden = this.hideCursor();
2473 var cx = this.cursorX;
2474 var cy = this.cursorY;
2475 var s = this.spaces(w);
2476 for (var i = y+h; i-- > y; ) {
2477 this.putString(x, i, s, color, style);
2479 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2483 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2485 var className = [ ];
2487 var console = this.console[this.currentScreen];
2488 if (sY >= console.childNodes.length) {
2489 text[0] = this.spaces(w);
2490 className[0] = undefined;
2491 style[0] = undefined;
2493 var line = console.childNodes[sY];
2494 if (line.tagName != 'DIV' || !line.childNodes.length) {
2495 text[0] = this.spaces(w);
2496 className[0] = undefined;
2497 style[0] = undefined;
2500 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2501 var s = this.getTextContent(span);
2504 var o = sX > x ? sX - x : 0;
2505 text[text.length] = s.substr(o, w);
2506 className[className.length] = span.className;
2507 style[style.length] = span.style.cssText;
2513 text[text.length] = this.spaces(w);
2514 className[className.length] = undefined;
2515 style[style.length] = undefined;
2519 var hidden = this.hideCursor();
2520 var cx = this.cursorX;
2521 var cy = this.cursorY;
2522 for (var i = 0; i < text.length; i++) {
2525 color = className[i];
2527 color = 'ansi0 bgAnsi15';
2529 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2530 dX += text[i].length;
2532 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2535 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2537 var left = incX < 0 ? -incX : 0;
2538 var right = incX > 0 ? incX : 0;
2539 var up = incY < 0 ? -incY : 0;
2540 var down = incY > 0 ? incY : 0;
2542 // Clip region against terminal size
2543 var dontScroll = null;
2548 if (w > this.terminalWidth - right) {
2549 w = this.terminalWidth - right;
2551 if ((w -= x) <= 0) {
2558 if (h > this.terminalHeight - down) {
2559 h = this.terminalHeight - down;
2565 if (style && style.indexOf('underline')) {
2566 // Different terminal emulators disagree on the attributes that
2567 // are used for scrolling. The consensus seems to be, never to
2568 // fill with underlined spaces. N.B. this is different from the
2569 // cases when the user blanks a region. User-initiated blanking
2570 // always fills with all of the current attributes.
2571 style = style.replace(/text-decoration:underline;/, '');
2574 // Compute current scroll position
2575 var scrollPos = this.numScrollbackLines -
2576 (this.scrollable.scrollTop-1) / this.cursorHeight;
2578 // Determine original cursor position. Hide cursor temporarily to avoid
2579 // visual artifacts.
2580 var hidden = this.hideCursor();
2581 var cx = this.cursorX;
2582 var cy = this.cursorY;
2583 var console = this.console[this.currentScreen];
2585 if (!incX && !x && w == this.terminalWidth) {
2586 // Scrolling entire lines
2589 if (!this.currentScreen && y == -incY &&
2590 h == this.terminalHeight + incY) {
2591 // Scrolling up with adding to the scrollback buffer. This is only
2592 // possible if there are at least as many lines in the console,
2593 // as the terminal is high
2594 while (console.childNodes.length < this.terminalHeight) {
2595 this.insertBlankLine(this.terminalHeight);
2598 // Add new lines at bottom in order to force scrolling
2599 for (var i = 0; i < y; i++) {
2600 this.insertBlankLine(console.childNodes.length, color, style);
2603 // Adjust the number of lines in the scrollback buffer by
2604 // removing excess entries.
2605 this.updateNumScrollbackLines();
2606 while (this.numScrollbackLines >
2607 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2608 console.removeChild(console.firstChild);
2609 this.numScrollbackLines--;
2612 // Mark lines in the scrollback buffer, so that they do not get
2614 for (var i = this.numScrollbackLines, j = -incY;
2615 i-- > 0 && j-- > 0; ) {
2616 console.childNodes[i].className = 'scrollback';
2619 // Scrolling up without adding to the scrollback buffer.
2622 console.childNodes.length >
2623 this.numScrollbackLines + y + incY; ) {
2624 console.removeChild(console.childNodes[
2625 this.numScrollbackLines + y + incY]);
2628 // If we used to have a scrollback buffer, then we must make sure
2629 // that we add back blank lines at the bottom of the terminal.
2630 // Similarly, if we are scrolling in the middle of the screen,
2631 // we must add blank lines to ensure that the bottom of the screen
2632 // does not move up.
2633 if (this.numScrollbackLines > 0 ||
2634 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2635 for (var i = -incY; i-- > 0; ) {
2636 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2645 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2646 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2648 for (var i = incY; i--; ) {
2649 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2653 // Scrolling partial lines
2655 // Scrolling up or horizontally within a line
2656 for (var i = y + this.numScrollbackLines;
2657 i < y + this.numScrollbackLines + h;
2659 this.copyLineSegment(x + incX, i + incY, x, i, w);
2663 for (var i = y + this.numScrollbackLines + h;
2664 i-- > y + this.numScrollbackLines; ) {
2665 this.copyLineSegment(x + incX, i + incY, x, i, w);
2669 // Clear blank regions
2671 this.clearRegion(x, y, incX, h, color, style);
2672 } else if (incX < 0) {
2673 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2676 this.clearRegion(x, y, w, incY, color, style);
2677 } else if (incY < 0) {
2678 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2682 // Reset scroll position
2683 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2684 this.cursorHeight + 1;
2686 // Move cursor back to its original position
2687 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2691 VT100.prototype.copy = function(selection) {
2692 if (selection == undefined) {
2693 selection = this.selection();
2695 this.internalClipboard = undefined;
2696 if (selection.length) {
2699 this.cliphelper.value = selection;
2700 this.cliphelper.select();
2701 this.cliphelper.createTextRange().execCommand('copy');
2703 this.internalClipboard = selection;
2705 this.cliphelper.value = '';
2709 VT100.prototype.copyLast = function() {
2710 // Opening the context menu can remove the selection. We try to prevent this
2711 // from happening, but that is not possible for all browsers. So, instead,
2712 // we compute the selection before showing the menu.
2713 this.copy(this.lastSelection);
2716 VT100.prototype.pasteFnc = function() {
2717 var clipboard = undefined;
2718 if (this.internalClipboard != undefined) {
2719 clipboard = this.internalClipboard;
2722 this.cliphelper.value = '';
2723 this.cliphelper.createTextRange().execCommand('paste');
2724 clipboard = this.cliphelper.value;
2728 this.cliphelper.value = '';
2729 if (clipboard && this.menu.style.visibility == 'hidden') {
2731 this.keysPressed('' + clipboard);
2738 VT100.prototype.pasteBrowserFnc = function() {
2739 var clipboard = prompt("Paste into this box:","");
2740 if (clipboard != undefined) {
2741 return this.keysPressed('' + clipboard);
2745 VT100.prototype.toggleUTF = function() {
2746 this.utfEnabled = !this.utfEnabled;
2748 // We always persist the last value that the user selected. Not necessarily
2749 // the last value that a random program requested.
2750 this.utfPreferred = this.utfEnabled;
2753 VT100.prototype.toggleBell = function() {
2754 this.visualBell = !this.visualBell;
2757 VT100.prototype.toggleSoftKeyboard = function() {
2758 this.softKeyboard = !this.softKeyboard;
2759 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2762 VT100.prototype.deselectKeys = function(elem) {
2763 if (elem && elem.className == 'selected') {
2764 elem.className = '';
2766 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2767 this.deselectKeys(elem);
2771 VT100.prototype.showSoftKeyboard = function() {
2772 // Make sure no key is currently selected
2773 this.lastSelectedKey = undefined;
2774 this.deselectKeys(this.keyboard);
2775 this.isShift = false;
2776 this.showShiftState(false);
2777 this.isCtrl = false;
2778 this.showCtrlState(false);
2780 this.showAltState(false);
2782 this.keyboard.style.left = '0px';
2783 this.keyboard.style.top = '0px';
2784 this.keyboard.style.width = this.container.offsetWidth + 'px';
2785 this.keyboard.style.height = this.container.offsetHeight + 'px';
2786 this.keyboard.style.visibility = 'hidden';
2787 this.keyboard.style.display = '';
2789 var kbd = this.keyboard.firstChild;
2791 var transform = this.getTransformName();
2793 kbd.style[transform] = '';
2794 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2795 scale = (kbd.offsetWidth/
2796 this.container.offsetWidth)/0.9;
2798 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2799 scale = Math.max((kbd.offsetHeight/
2800 this.container.offsetHeight)/0.9);
2802 var style = this.getTransformStyle(transform,
2803 scale > 1.0 ? scale : undefined);
2804 kbd.style[transform] = style;
2806 if (transform == 'filter') {
2809 kbd.style.left = ((this.container.offsetWidth -
2810 kbd.offsetWidth/scale)/2) + 'px';
2811 kbd.style.top = ((this.container.offsetHeight -
2812 kbd.offsetHeight/scale)/2) + 'px';
2814 this.keyboard.style.visibility = 'visible';
2817 VT100.prototype.hideSoftKeyboard = function() {
2818 this.keyboard.style.display = 'none';
2821 VT100.prototype.toggleCursorBlinking = function() {
2822 this.blinkingCursor = !this.blinkingCursor;
2825 VT100.prototype.about = function() {
2826 alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2827 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2828 "For more information check http://shellinabox.com");
2831 VT100.prototype.hideContextMenu = function() {
2832 this.menu.style.visibility = 'hidden';
2833 this.menu.style.top = '-100px';
2834 this.menu.style.left = '-100px';
2835 this.menu.style.width = '0px';
2836 this.menu.style.height = '0px';
2839 VT100.prototype.extendContextMenu = function(entries, actions) {
2842 VT100.prototype.showContextMenu = function(x, y) {
2843 this.menu.innerHTML =
2844 '<table class="popup" ' +
2845 'cellpadding="0" cellspacing="0">' +
2847 '<ul id="menuentries">' +
2848 '<li id="beginclipboard">Copy</li>' +
2849 '<li id="endclipboard">Paste</li>' +
2850 '<li id="browserclipboard">Paste from browser</li>' +
2852 '<li id="reset">Reset</li>' +
2854 '<li id="beginconfig">' +
2855 (this.utfEnabled ? '<img src="/webshell/enabled.gif" />' : '') +
2858 (this.visualBell ? '<img src="/webshell/enabled.gif" />' : '') +
2861 (this.softKeyboard ? '<img src="/webshell/enabled.gif" />' : '') +
2862 'Onscreen Keyboard</li>' +
2863 '<li id="endconfig">' +
2864 (this.blinkingCursor ? '<img src="/webshell/enabled.gif" />' : '') +
2865 'Blinking Cursor</li>'+
2866 (this.usercss.firstChild ?
2867 '<hr id="beginusercss" />' +
2868 this.usercss.innerHTML +
2869 '<hr id="endusercss" />' :
2871 '<li id="about">About...</li>' +
2876 var popup = this.menu.firstChild;
2877 var menuentries = this.getChildById(popup, 'menuentries');
2879 // Determine menu entries that should be disabled
2880 this.lastSelection = this.selection();
2881 if (!this.lastSelection.length) {
2882 menuentries.firstChild.className
2885 var p = this.pasteFnc();
2887 menuentries.childNodes[1].className
2891 // Actions for default items
2892 var actions = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2893 this.toggleUTF, this.toggleBell,
2894 this.toggleSoftKeyboard,
2895 this.toggleCursorBlinking ];
2897 // Actions for user CSS styles (if any)
2898 for (var i = 0; i < this.usercssActions.length; ++i) {
2899 actions[actions.length] = this.usercssActions[i];
2901 actions[actions.length] = this.about;
2903 // Allow subclasses to dynamically add entries to the context menu
2904 this.extendContextMenu(menuentries, actions);
2906 // Hook up event listeners
2907 for (var node = menuentries.firstChild, i = 0; node;
2908 node = node.nextSibling) {
2909 if (node.tagName == 'LI') {
2910 if (node.className != 'disabled') {
2911 this.addListener(node, 'mouseover',
2912 function(vt100, node) {
2914 node.className = 'hover';
2917 this.addListener(node, 'mouseout',
2918 function(vt100, node) {
2920 node.className = '';
2923 this.addListener(node, 'mousedown',
2924 function(vt100, action) {
2925 return function(event) {
2926 vt100.hideContextMenu();
2928 vt100.storeUserSettings();
2929 return vt100.cancelEvent(event || window.event);
2931 }(this, actions[i]));
2932 this.addListener(node, 'mouseup',
2934 return function(event) {
2935 return vt100.cancelEvent(event || window.event);
2938 this.addListener(node, 'mouseclick',
2940 return function(event) {
2941 return vt100.cancelEvent(event || window.event);
2949 // Position menu next to the mouse pointer
2950 this.menu.style.left = '0px';
2951 this.menu.style.top = '0px';
2952 this.menu.style.width = this.container.offsetWidth + 'px';
2953 this.menu.style.height = this.container.offsetHeight + 'px';
2954 popup.style.left = '0px';
2955 popup.style.top = '0px';
2958 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2959 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2964 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2965 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2970 popup.style.left = x + 'px';
2971 popup.style.top = y + 'px';
2973 // Block all other interactions with the terminal emulator
2974 this.addListener(this.menu, 'click', function(vt100) {
2976 vt100.hideContextMenu();
2981 this.menu.style.visibility = '';
2984 VT100.prototype.keysPressed = function(ch) {
2985 for (var i = 0; i < ch.length; i++) {
2986 var c = ch.charCodeAt(i);
2987 this.vt100(c >= 7 && c <= 15 ||
2988 c == 24 || c == 26 || c == 27 || c >= 32
2989 ? String.fromCharCode(c) : '<' + c + '>');
2993 VT100.prototype.applyModifiers = function(ch, event) {
2995 if (event.ctrlKey) {
2996 if (ch >= 32 && ch <= 127) {
2997 // For historic reasons, some control characters are treated specially
2999 case /* 3 */ 51: ch = 27; break;
3000 case /* 4 */ 52: ch = 28; break;
3001 case /* 5 */ 53: ch = 29; break;
3002 case /* 6 */ 54: ch = 30; break;
3003 case /* 7 */ 55: ch = 31; break;
3004 case /* 8 */ 56: ch = 127; break;
3005 case /* ? */ 63: ch = 127; break;
3006 default: ch &= 31; break;
3010 return String.fromCharCode(ch);
3016 VT100.prototype.handleKey = function(event) {
3017 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
3018 // (event.shiftKey || event.ctrlKey || event.altKey ||
3019 // event.metaKey ? ', ' +
3020 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3021 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3024 if (typeof event.charCode != 'undefined') {
3025 // non-IE keypress events have a translated charCode value. Also, our
3026 // fake events generated when receiving keydown events include this data
3028 ch = event.charCode;
3029 key = event.keyCode;
3031 // When sending a keypress event, IE includes the translated character
3032 // code in the keyCode field.
3037 // Apply modifier keys (ctrl and shift)
3041 ch = this.applyModifiers(ch, event);
3043 // By this point, "ch" is either defined and contains the character code, or
3044 // it is undefined and "key" defines the code of a function key
3045 if (ch != undefined) {
3046 this.scrollable.scrollTop = this.numScrollbackLines *
3047 this.cursorHeight + 1;
3049 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
3050 // Many programs have difficulties dealing with parametrized escape
3051 // sequences for function keys. Thus, if ALT is the only modifier
3052 // key, return Emacs-style keycodes for commonly used keys.
3054 case 33: /* Page Up */ ch = '\u001B<'; break;
3055 case 34: /* Page Down */ ch = '\u001B>'; break;
3056 case 37: /* Left */ ch = '\u001Bb'; break;
3057 case 38: /* Up */ ch = '\u001Bp'; break;
3058 case 39: /* Right */ ch = '\u001Bf'; break;
3059 case 40: /* Down */ ch = '\u001Bn'; break;
3060 case 46: /* Delete */ ch = '\u001Bd'; break;
3063 } else if (event.shiftKey && !event.ctrlKey &&
3064 !event.altKey && !event.metaKey) {
3066 case 33: /* Page Up */ this.scrollBack(); return;
3067 case 34: /* Page Down */ this.scrollFore(); return;
3071 if (ch == undefined) {
3073 case 8: /* Backspace */ ch = '\u007f'; break;
3074 case 9: /* Tab */ ch = '\u0009'; break;
3075 case 10: /* Return */ ch = '\u000A'; break;
3076 case 13: /* Enter */ ch = this.crLfMode ?
3077 '\r\n' : '\r'; break;
3078 case 16: /* Shift */ return;
3079 case 17: /* Ctrl */ return;
3080 case 18: /* Alt */ return;
3081 case 19: /* Break */ return;
3082 case 20: /* Caps Lock */ return;
3083 case 27: /* Escape */ ch = '\u001B'; break;
3084 case 33: /* Page Up */ ch = '\u001B[5~'; break;
3085 case 34: /* Page Down */ ch = '\u001B[6~'; break;
3086 case 35: /* End */ ch = '\u001BOF'; break;
3087 case 36: /* Home */ ch = '\u001BOH'; break;
3088 case 37: /* Left */ ch = this.cursorKeyMode ?
3089 '\u001BOD' : '\u001B[D'; break;
3090 case 38: /* Up */ ch = this.cursorKeyMode ?
3091 '\u001BOA' : '\u001B[A'; break;
3092 case 39: /* Right */ ch = this.cursorKeyMode ?
3093 '\u001BOC' : '\u001B[C'; break;
3094 case 40: /* Down */ ch = this.cursorKeyMode ?
3095 '\u001BOB' : '\u001B[B'; break;
3096 case 45: /* Insert */ ch = '\u001B[2~'; break;
3097 case 46: /* Delete */ ch = '\u001B[3~'; break;
3098 case 91: /* Left Window */ return;
3099 case 92: /* Right Window */ return;
3100 case 93: /* Select */ return;
3101 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
3102 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
3103 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
3104 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
3105 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
3106 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
3107 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
3108 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
3109 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
3110 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
3111 case 106: /* * */ ch = this.applyModifiers(42, event); break;
3112 case 107: /* + */ ch = this.applyModifiers(43, event); break;
3113 case 109: /* - */ ch = this.applyModifiers(45, event); break;
3114 case 110: /* . */ ch = this.applyModifiers(46, event); break;
3115 case 111: /* / */ ch = this.applyModifiers(47, event); break;
3116 case 112: /* F1 */ ch = '\u001BOP'; break;
3117 case 113: /* F2 */ ch = '\u001BOQ'; break;
3118 case 114: /* F3 */ ch = '\u001BOR'; break;
3119 case 115: /* F4 */ ch = '\u001BOS'; break;
3120 case 116: /* F5 */ ch = '\u001B[15~'; break;
3121 case 117: /* F6 */ ch = '\u001B[17~'; break;
3122 case 118: /* F7 */ ch = '\u001B[18~'; break;
3123 case 119: /* F8 */ ch = '\u001B[19~'; break;
3124 case 120: /* F9 */ ch = '\u001B[20~'; break;
3125 case 121: /* F10 */ ch = '\u001B[21~'; break;
3126 case 122: /* F11 */ ch = '\u001B[23~'; break;
3127 case 123: /* F12 */ ch = '\u001B[24~'; break;
3128 case 144: /* Num Lock */ return;
3129 case 145: /* Scroll Lock */ return;
3130 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
3131 case 187: /* = */ ch = this.applyModifiers(61, event); break;
3132 case 188: /* , */ ch = this.applyModifiers(44, event); break;
3133 case 189: /* - */ ch = this.applyModifiers(45, event); break;
3134 case 173: /* - */ ch = this.applyModifiers(45, event); break; // FF15 Patch
3135 case 190: /* . */ ch = this.applyModifiers(46, event); break;
3136 case 191: /* / */ ch = this.applyModifiers(47, event); break;
3137 // Conflicts with dead key " on Swiss keyboards
3138 //case 192: /* ` */ ch = this.applyModifiers(96, event); break;
3139 // Conflicts with dead key " on Swiss keyboards
3140 //case 219: /* [ */ ch = this.applyModifiers(91, event); break;
3141 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
3142 // Conflicts with dead key ^ and ` on Swiss keaboards
3143 // ^ and " on French keyboards
3144 //case 221: /* ] */ ch = this.applyModifiers(93, event); break;
3145 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
3148 this.scrollable.scrollTop = this.numScrollbackLines *
3149 this.cursorHeight + 1;
3153 // "ch" now contains the sequence of keycodes to send. But we might still
3154 // have to apply the effects of modifier keys.
3155 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
3156 var start, digit, part1, part2;
3157 if ((start = ch.substr(0, 2)) == '\u001B[') {
3159 part1.length < ch.length &&
3160 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
3161 part1 = ch.substr(0, part1.length + 1);
3163 part2 = ch.substr(part1.length);
3164 if (part1.length > 2) {
3167 } else if (start == '\u001BO') {
3169 part2 = ch.substr(2);
3171 if (part1 != undefined) {
3173 ((event.shiftKey ? 1 : 0) +
3174 (event.altKey|event.metaKey ? 2 : 0) +
3175 (event.ctrlKey ? 4 : 0)) +
3177 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
3182 if (this.menu.style.visibility == 'hidden') {
3183 // this.vt100('R: c=');
3184 // for (var i = 0; i < ch.length; i++)
3185 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
3186 // this.vt100('\r\n');
3187 this.keysPressed(ch);
3191 VT100.prototype.inspect = function(o, d) {
3192 if (d == undefined) {
3196 if (typeof o == 'object' && ++d < 2) {
3199 rc += this.spaces(d * 2) + i + ' -> ';
3201 rc += this.inspect(o[i], d);
3203 rc += '?' + '?' + '?\r\n';
3208 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
3213 VT100.prototype.checkComposedKeys = function(event) {
3214 // Composed keys (at least on Linux) do not generate normal events.
3215 // Instead, they get entered into the text field. We normally catch
3216 // this on the next keyup event.
3217 var s = this.input.value;
3219 this.input.value = '';
3220 if (this.menu.style.visibility == 'hidden') {
3221 this.keysPressed(s);
3226 VT100.prototype.fixEvent = function(event) {
3227 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
3228 // is used as a second-level selector, clear the modifier bits before
3229 // handling the event.
3230 if (event.ctrlKey && event.altKey) {
3232 fake.charCode = event.charCode;
3233 fake.keyCode = event.keyCode;
3234 fake.ctrlKey = false;
3235 fake.shiftKey = event.shiftKey;
3236 fake.altKey = false;
3237 fake.metaKey = event.metaKey;
3241 // Some browsers fail to translate keys, if both shift and alt/meta is
3242 // pressed at the same time. We try to translate those cases, but that
3243 // only works for US keyboard layouts.
3244 if (event.shiftKey) {
3247 switch (this.lastNormalKeyDownEvent.keyCode) {
3248 case 39: /* ' -> " */ u = 39; s = 34; break;
3249 case 44: /* , -> < */ u = 44; s = 60; break;
3250 case 45: /* - -> _ */ u = 45; s = 95; break;
3251 case 46: /* . -> > */ u = 46; s = 62; break;
3252 case 47: /* / -> ? */ u = 47; s = 63; break;
3254 case 48: /* 0 -> ) */ u = 48; s = 41; break;
3255 case 49: /* 1 -> ! */ u = 49; s = 33; break;
3256 case 50: /* 2 -> @ */ u = 50; s = 64; break;
3257 case 51: /* 3 -> # */ u = 51; s = 35; break;
3258 case 52: /* 4 -> $ */ u = 52; s = 36; break;
3259 case 53: /* 5 -> % */ u = 53; s = 37; break;
3260 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
3261 case 55: /* 7 -> & */ u = 55; s = 38; break;
3262 case 56: /* 8 -> * */ u = 56; s = 42; break;
3263 case 57: /* 9 -> ( */ u = 57; s = 40; break;
3265 case 59: /* ; -> : */ u = 59; s = 58; break;
3266 case 61: /* = -> + */ u = 61; s = 43; break;
3267 case 91: /* [ -> { */ u = 91; s = 123; break;
3268 case 92: /* \ -> | */ u = 92; s = 124; break;
3269 case 93: /* ] -> } */ u = 93; s = 125; break;
3270 case 96: /* ` -> ~ */ u = 96; s = 126; break;
3272 case 109: /* - -> _ */ u = 45; s = 95; break;
3273 case 111: /* / -> ? */ u = 47; s = 63; break;
3275 case 186: /* ; -> : */ u = 59; s = 58; break;
3276 case 187: /* = -> + */ u = 61; s = 43; break;
3277 case 188: /* , -> < */ u = 44; s = 60; break;
3278 case 189: /* - -> _ */ u = 45; s = 95; break;
3279 case 173: /* - -> _ */ u = 45; s = 95; break; // FF15 Patch
3280 case 190: /* . -> > */ u = 46; s = 62; break;
3281 case 191: /* / -> ? */ u = 47; s = 63; break;
3282 case 192: /* ` -> ~ */ u = 96; s = 126; break;
3283 case 219: /* [ -> { */ u = 91; s = 123; break;
3284 case 220: /* \ -> | */ u = 92; s = 124; break;
3285 case 221: /* ] -> } */ u = 93; s = 125; break;
3286 case 222: /* ' -> " */ u = 39; s = 34; break;
3289 if (s && (event.charCode == u || event.charCode == 0)) {
3292 fake.keyCode = event.keyCode;
3293 fake.ctrlKey = event.ctrlKey;
3294 fake.shiftKey = event.shiftKey;
3295 fake.altKey = event.altKey;
3296 fake.metaKey = event.metaKey;
3303 VT100.prototype.keyDown = function(event) {
3304 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
3305 // (event.shiftKey || event.ctrlKey || event.altKey ||
3306 // event.metaKey ? ', ' +
3307 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3308 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3310 this.checkComposedKeys(event);
3311 this.lastKeyPressedEvent = undefined;
3312 this.lastKeyDownEvent = undefined;
3313 this.lastNormalKeyDownEvent = event;
3315 // Swiss keyboard conflicts:
3322 // French keyoard conflicts:
3326 event.keyCode == 32 ||
3327 event.keyCode >= 48 && event.keyCode <= 57 ||
3328 event.keyCode >= 65 && event.keyCode <= 90;
3331 event.keyCode == 59 ||
3332 event.keyCode >= 96 && event.keyCode <= 105 ||
3333 event.keyCode == 107 ||
3334 event.keyCode == 192 ||
3335 event.keyCode >= 219 && event.keyCode <= 221 ||
3336 event.keyCode == 223 ||
3337 event.keyCode == 226;
3340 event.keyCode == 61 ||
3341 event.keyCode == 106 ||
3342 event.keyCode >= 109 && event.keyCode <= 111 ||
3343 event.keyCode >= 186 && event.keyCode <= 191 ||
3344 event.keyCode == 222 ||
3345 event.keyCode == 252;
3347 if (navigator.appName == 'Konqueror') {
3348 normalKey |= event.keyCode < 128;
3353 // We normally prefer to look at keypress events, as they perform the
3354 // translation from keyCode to charCode. This is important, as the
3355 // translation is locale-dependent.
3356 // But for some keys, we must intercept them during the keydown event,
3357 // as they would otherwise get interpreted by the browser.
3358 // Even, when doing all of this, there are some keys that we can never
3359 // intercept. This applies to some of the menu navigation keys in IE.
3360 // In fact, we see them, but we cannot stop IE from seeing them, too.
3361 if ((event.charCode || event.keyCode) &&
3362 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3364 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3365 // interpret this sequence ourselves, as some keyboard layouts use
3366 // it for second-level layouts.
3367 !(event.ctrlKey && event.altKey)) ||
3368 this.catchModifiersEarly && normalKey && !alphNumKey &&
3369 (event.ctrlKey || event.altKey || event.metaKey) ||
3371 this.lastKeyDownEvent = event;
3373 fake.ctrlKey = event.ctrlKey;
3374 fake.shiftKey = event.shiftKey;
3375 fake.altKey = event.altKey;
3376 fake.metaKey = event.metaKey;
3378 fake.charCode = event.keyCode;
3382 fake.keyCode = event.keyCode;
3383 if (!alphNumKey && event.shiftKey) {
3384 fake = this.fixEvent(fake);
3388 this.handleKey(fake);
3389 this.lastNormalKeyDownEvent = undefined;
3392 // For non-IE browsers
3393 event.stopPropagation();
3394 event.preventDefault();
3399 event.cancelBubble = true;
3400 event.returnValue = false;
3410 VT100.prototype.keyPressed = function(event) {
3411 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3412 // (event.shiftKey || event.ctrlKey || event.altKey ||
3413 // event.metaKey ? ', ' +
3414 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3415 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3417 if (this.lastKeyDownEvent) {
3418 // If we already processed the key on keydown, do not process it
3419 // again here. Ideally, the browser should not even have generated a
3420 // keypress event in this case. But that does not appear to always work.
3421 this.lastKeyDownEvent = undefined;
3423 this.handleKey(event.altKey || event.metaKey
3424 ? this.fixEvent(event) : event);
3428 // For non-IE browsers
3429 event.preventDefault();
3435 event.cancelBubble = true;
3436 event.returnValue = false;
3441 this.lastNormalKeyDownEvent = undefined;
3442 this.lastKeyPressedEvent = event;
3446 VT100.prototype.keyUp = function(event) {
3447 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3448 // (event.shiftKey || event.ctrlKey || event.altKey ||
3449 // event.metaKey ? ', ' +
3450 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3451 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3453 if (this.lastKeyPressedEvent) {
3454 // The compose key on Linux occasionally confuses the browser and keeps
3455 // inserting bogus characters into the input field, even if just a regular
3456 // key has been pressed. Detect this case and drop the bogus characters.
3458 event.srcElement).value = '';
3460 // This is usually were we notice that a key has been composed and
3461 // thus failed to generate normal events.
3462 this.checkComposedKeys(event);
3464 // Some browsers don't report keypress events if ctrl or alt is pressed
3465 // for non-alphanumerical keys. Patch things up for now, but in the
3466 // future we will catch these keys earlier (in the keydown handler).
3467 if (this.lastNormalKeyDownEvent) {
3468 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3469 this.catchModifiersEarly = true;
3471 event.keyCode == 32 ||
3472 // Conflicts with dead key ~ (code 50) on French keyboards
3473 //event.keyCode >= 48 && event.keyCode <= 57 ||
3474 event.keyCode >= 48 && event.keyCode <= 49 ||
3475 event.keyCode >= 51 && event.keyCode <= 57 ||
3476 event.keyCode >= 65 && event.keyCode <= 90;
3479 event.keyCode == 50 ||
3480 event.keyCode >= 96 && event.keyCode <= 105;
3483 event.keyCode == 59 || event.keyCode == 61 ||
3484 event.keyCode == 106 || event.keyCode == 107 ||
3485 event.keyCode >= 109 && event.keyCode <= 111 ||
3486 event.keyCode >= 186 && event.keyCode <= 192 ||
3487 event.keyCode >= 219 && event.keyCode <= 223 ||
3488 event.keyCode == 252;
3490 fake.ctrlKey = event.ctrlKey;
3491 fake.shiftKey = event.shiftKey;
3492 fake.altKey = event.altKey;
3493 fake.metaKey = event.metaKey;
3495 fake.charCode = event.keyCode;
3499 fake.keyCode = event.keyCode;
3500 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3501 fake = this.fixEvent(fake);
3504 this.lastNormalKeyDownEvent = undefined;
3505 this.handleKey(fake);
3511 event.cancelBubble = true;
3512 event.returnValue = false;
3517 this.lastKeyDownEvent = undefined;
3518 this.lastKeyPressedEvent = undefined;
3522 VT100.prototype.animateCursor = function(inactive) {
3523 if (!this.cursorInterval) {
3524 this.cursorInterval = setInterval(
3527 vt100.animateCursor();
3529 // Use this opportunity to check whether the user entered a composed
3530 // key, or whether somebody pasted text into the textfield.
3531 vt100.checkComposedKeys();
3535 if (inactive != undefined || this.cursor.className != 'inactive') {
3537 this.cursor.className = 'inactive';
3539 if (this.blinkingCursor) {
3540 this.cursor.className = this.cursor.className == 'bright'
3543 this.cursor.className = 'bright';
3549 VT100.prototype.blurCursor = function() {
3550 this.animateCursor(true);
3553 VT100.prototype.focusCursor = function() {
3554 this.animateCursor(false);
3557 VT100.prototype.flashScreen = function() {
3558 this.isInverted = !this.isInverted;
3559 this.refreshInvertedState();
3560 this.isInverted = !this.isInverted;
3561 setTimeout(function(vt100) {
3563 vt100.refreshInvertedState();
3568 VT100.prototype.beep = function() {
3569 if (this.visualBell) {
3576 this.beeper.src = 'beep.wav';
3583 VT100.prototype.bs = function() {
3584 if (this.cursorX > 0) {
3585 this.gotoXY(this.cursorX - 1, this.cursorY);
3586 this.needWrap = false;
3590 VT100.prototype.ht = function(count) {
3591 if (count == undefined) {
3594 var cx = this.cursorX;
3595 while (count-- > 0) {
3596 while (cx++ < this.terminalWidth) {
3597 var tabState = this.userTabStop[cx];
3598 if (tabState == false) {
3599 // Explicitly cleared tab stop
3601 } else if (tabState) {
3602 // Explicitly set tab stop
3605 // Default tab stop at each eighth column
3612 if (cx > this.terminalWidth - 1) {
3613 cx = this.terminalWidth - 1;
3615 if (cx != this.cursorX) {
3616 this.gotoXY(cx, this.cursorY);
3620 VT100.prototype.rt = function(count) {
3621 if (count == undefined) {
3624 var cx = this.cursorX;
3625 while (count-- > 0) {
3627 var tabState = this.userTabStop[cx];
3628 if (tabState == false) {
3629 // Explicitly cleared tab stop
3631 } else if (tabState) {
3632 // Explicitly set tab stop
3635 // Default tab stop at each eighth column
3645 if (cx != this.cursorX) {
3646 this.gotoXY(cx, this.cursorY);
3650 VT100.prototype.cr = function() {
3651 this.gotoXY(0, this.cursorY);
3652 this.needWrap = false;
3655 VT100.prototype.lf = function(count) {
3656 if (count == undefined) {
3659 if (count > this.terminalHeight) {
3660 count = this.terminalHeight;
3666 while (count-- > 0) {
3667 if (this.cursorY == this.bottom - 1) {
3668 this.scrollRegion(0, this.top + 1,
3669 this.terminalWidth, this.bottom - this.top - 1,
3670 0, -1, this.color, this.style);
3672 } else if (this.cursorY < this.terminalHeight - 1) {
3673 this.gotoXY(this.cursorX, this.cursorY + 1);
3678 VT100.prototype.ri = function(count) {
3679 if (count == undefined) {
3682 if (count > this.terminalHeight) {
3683 count = this.terminalHeight;
3689 while (count-- > 0) {
3690 if (this.cursorY == this.top) {
3691 this.scrollRegion(0, this.top,
3692 this.terminalWidth, this.bottom - this.top - 1,
3693 0, 1, this.color, this.style);
3694 } else if (this.cursorY > 0) {
3695 this.gotoXY(this.cursorX, this.cursorY - 1);
3698 this.needWrap = false;
3701 VT100.prototype.respondID = function() {
3702 this.respondString += '\u001B[?6c';
3705 VT100.prototype.respondSecondaryDA = function() {
3706 this.respondString += '\u001B[>0;0;0c';
3710 VT100.prototype.updateStyle = function() {
3712 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3713 this.style = 'text-decoration: underline;';
3715 var bg = (this.attr >> 4) & 0xF;
3716 var fg = this.attr & 0xF;
3717 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3722 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3723 fg = 8; // Dark grey
3724 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3726 this.style = 'font-weight: bold;';
3728 if (this.attr & 0x1000 /* ATTR_BLINK */) {
3729 this.style = 'text-decoration: blink;';
3731 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3734 VT100.prototype.setAttrColors = function(attr) {
3735 if (attr != this.attr) {
3741 VT100.prototype.saveCursor = function() {
3742 this.savedX[this.currentScreen] = this.cursorX;
3743 this.savedY[this.currentScreen] = this.cursorY;
3744 this.savedAttr[this.currentScreen] = this.attr;
3745 this.savedUseGMap = this.useGMap;
3746 for (var i = 0; i < 4; i++) {
3747 this.savedGMap[i] = this.GMap[i];
3749 this.savedValid[this.currentScreen] = true;
3752 VT100.prototype.restoreCursor = function() {
3753 if (!this.savedValid[this.currentScreen]) {
3756 this.attr = this.savedAttr[this.currentScreen];
3758 this.useGMap = this.savedUseGMap;
3759 for (var i = 0; i < 4; i++) {
3760 this.GMap[i] = this.savedGMap[i];
3762 this.translate = this.GMap[this.useGMap];
3763 this.needWrap = false;
3764 this.gotoXY(this.savedX[this.currentScreen],
3765 this.savedY[this.currentScreen]);
3768 VT100.prototype.getTransformName = function() {
3769 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3770 for (var i = 0; i < styles.length; ++i) {
3771 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3778 VT100.prototype.getTransformStyle = function(transform, scale) {
3779 return scale && scale != 1.0
3780 ? transform == 'filter'
3781 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3782 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3783 "sizingMethod='auto expand')"
3784 : 'translateX(-50%) ' +
3785 'scaleX(' + (1.0/scale) + ') ' +
3790 VT100.prototype.set80_132Mode = function(state) {
3791 var transform = this.getTransformName();
3793 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3797 this.getTransformStyle(transform, 1.65):'';
3798 this.console[this.currentScreen].style[transform] = style;
3799 this.cursor.style[transform] = style;
3800 this.space.style[transform] = style;
3801 this.scale = state ? 1.65 : 1.0;
3802 if (transform == 'filter') {
3803 this.console[this.currentScreen].style.width = state ? '165%' : '';
3809 VT100.prototype.setMode = function(state) {
3810 for (var i = 0; i <= this.npar; i++) {
3811 if (this.isQuestionMark) {
3812 switch (this.par[i]) {
3813 case 1: this.cursorKeyMode = state; break;
3814 case 3: this.set80_132Mode(state); break;
3815 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3816 case 6: this.offsetMode = state; break;
3817 case 7: this.autoWrapMode = state; break;
3819 case 9: this.mouseReporting = state; break;
3820 case 25: this.cursorNeedsShowing = state;
3821 if (state) { this.showCursor(); }
3822 else { this.hideCursor(); } break;
3825 case 47: this.enableAlternateScreen(state); break;
3829 switch (this.par[i]) {
3830 case 3: this.dispCtrl = state; break;
3831 case 4: this.insertMode = state; break;
3832 case 20:this.crLfMode = state; break;
3839 VT100.prototype.statusReport = function() {
3840 // Ready and operational.
3841 this.respondString += '\u001B[0n';
3844 VT100.prototype.cursorReport = function() {
3845 this.respondString += '\u001B[' +
3846 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3848 (this.cursorX + 1) +
3852 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3853 // Changing of cursor color is not implemented.
3856 VT100.prototype.openPrinterWindow = function() {
3859 if (!this.printWin || this.printWin.closed) {
3860 this.printWin = window.open('', 'print-output',
3861 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3862 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3863 this.printWin.document.body.innerHTML =
3864 '<link rel="stylesheet" href="' +
3865 document.location.protocol + '//' + document.location.host +
3866 document.location.pathname.replace(/[^/]*$/, '') +
3867 'print-styles.css" type="text/css">\n' +
3868 '<div id="options"><input id="autoprint" type="checkbox"' +
3869 (this.autoprint ? ' checked' : '') + '>' +
3870 'Automatically, print page(s) when job is ready' +
3871 '</input></div>\n' +
3872 '<div id="spacer"><input type="checkbox"> </input></div>' +
3873 '<pre id="print"></pre>\n';
3874 var autoprint = this.printWin.document.getElementById('autoprint');
3875 this.addListener(autoprint, 'click',
3876 (function(vt100, autoprint) {
3878 vt100.autoprint = autoprint.checked;
3879 vt100.storeUserSettings();
3882 })(this, autoprint));
3883 this.printWin.document.title = 'ShellInABox Printer Output';
3886 // Maybe, a popup blocker prevented us from working. Better catch the
3887 // exception, so that we won't break the entire terminal session. The
3888 // user probably needs to disable the blocker first before retrying the
3892 rc &= this.printWin && !this.printWin.closed &&
3893 (this.printWin.innerWidth ||
3894 this.printWin.document.documentElement.clientWidth ||
3895 this.printWin.document.body.clientWidth) > 1;
3897 if (!rc && this.printing == 100) {
3898 // Different popup blockers work differently. We try to detect a couple
3899 // of common methods. And then we retry again a brief amount later, as
3900 // false positives are otherwise possible. If we are sure that there is
3901 // a popup blocker in effect, we alert the user to it. This is helpful
3902 // as some popup blockers have minimal or no UI, and the user might not
3903 // notice that they are missing the popup. In any case, we only show at
3904 // most one message per print job.
3905 this.printing = true;
3906 setTimeout((function(win) {
3908 if (!win || win.closed ||
3910 win.document.documentElement.clientWidth ||
3911 win.document.body.clientWidth) <= 1) {
3912 alert('Attempted to print, but a popup blocker ' +
3913 'prevented the printer window from opening');
3916 })(this.printWin), 2000);
3921 VT100.prototype.sendToPrinter = function(s) {
3922 this.openPrinterWindow();
3924 var doc = this.printWin.document;
3925 var print = doc.getElementById('print');
3926 if (print.lastChild && print.lastChild.nodeName == '#text') {
3927 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3929 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3932 // There probably was a more aggressive popup blocker that prevented us
3933 // from accessing the printer windows.
3937 VT100.prototype.sendControlToPrinter = function(ch) {
3938 // We get called whenever doControl() is active. But for the printer, we
3939 // only implement a basic line printer that doesn't understand most of
3940 // the escape sequences of the VT100 terminal. In fact, the only escape
3941 // sequence that we really need to recognize is '^[[5i' for turning the
3947 this.openPrinterWindow();
3948 var doc = this.printWin.document;
3949 var print = doc.getElementById('print');
3950 var chars = print.lastChild &&
3951 print.lastChild.nodeName == '#text' ?
3952 print.lastChild.textContent.length : 0;
3953 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3960 this.openPrinterWindow();
3961 var pageBreak = this.printWin.document.createElement('div');
3962 pageBreak.className = 'pagebreak';
3963 pageBreak.innerHTML = '<hr />';
3964 this.printWin.document.getElementById('print').appendChild(pageBreak);
3968 this.openPrinterWindow();
3969 var lineBreak = this.printWin.document.createElement('br');
3970 this.printWin.document.getElementById('print').appendChild(lineBreak);
3974 this.isEsc = 1 /* ESesc */;
3977 switch (this.isEsc) {
3979 this.isEsc = 0 /* ESnormal */;
3982 this.isEsc = 2 /* ESsquare */;
3988 case 2 /* ESsquare */:
3990 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3991 0, 0, 0, 0, 0, 0, 0, 0 ];
3992 this.isEsc = 3 /* ESgetpars */;
3993 this.isQuestionMark = ch == 0x3F /*?*/;
3994 if (this.isQuestionMark) {
3998 case 3 /* ESgetpars */:
3999 if (ch == 0x3B /*;*/) {
4002 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4003 var par = this.par[this.npar];
4004 if (par == undefined) {
4007 this.par[this.npar] = 10*par + (ch & 0xF);
4010 this.isEsc = 4 /* ESgotpars */;
4013 case 4 /* ESgotpars */:
4014 this.isEsc = 0 /* ESnormal */;
4015 if (this.isQuestionMark) {
4020 this.csii(this.par[0]);
4027 this.isEsc = 0 /* ESnormal */;
4033 // There probably was a more aggressive popup blocker that prevented us
4034 // from accessing the printer windows.
4038 VT100.prototype.csiAt = function(number) {
4043 if (number > this.terminalWidth - this.cursorX) {
4044 number = this.terminalWidth - this.cursorX;
4046 this.scrollRegion(this.cursorX, this.cursorY,
4047 this.terminalWidth - this.cursorX - number, 1,
4048 number, 0, this.color, this.style);
4049 this.needWrap = false;
4052 VT100.prototype.csii = function(number) {
4055 case 0: // Print Screen
4058 case 4: // Stop printing
4060 if (this.printing && this.printWin && !this.printWin.closed) {
4061 var print = this.printWin.document.getElementById('print');
4062 while (print.lastChild &&
4063 print.lastChild.tagName == 'DIV' &&
4064 print.lastChild.className == 'pagebreak') {
4065 // Remove trailing blank pages
4066 print.removeChild(print.lastChild);
4068 if (this.autoprint) {
4069 this.printWin.print();
4074 this.printing = false;
4076 case 5: // Start printing
4077 if (!this.printing && this.printWin && !this.printWin.closed) {
4078 this.printWin.document.getElementById('print').innerHTML = '';
4080 this.printing = 100;
4087 VT100.prototype.csiJ = function(number) {
4089 case 0: // Erase from cursor to end of display
4090 this.clearRegion(this.cursorX, this.cursorY,
4091 this.terminalWidth - this.cursorX, 1,
4092 this.color, this.style);
4093 if (this.cursorY < this.terminalHeight-2) {
4094 this.clearRegion(0, this.cursorY+1,
4095 this.terminalWidth, this.terminalHeight-this.cursorY-1,
4096 this.color, this.style);
4099 case 1: // Erase from start to cursor
4100 if (this.cursorY > 0) {
4101 this.clearRegion(0, 0,
4102 this.terminalWidth, this.cursorY,
4103 this.color, this.style);
4105 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4106 this.color, this.style);
4108 case 2: // Erase whole display
4109 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
4110 this.color, this.style);
4118 VT100.prototype.csiK = function(number) {
4120 case 0: // Erase from cursor to end of line
4121 this.clearRegion(this.cursorX, this.cursorY,
4122 this.terminalWidth - this.cursorX, 1,
4123 this.color, this.style);
4125 case 1: // Erase from start of line to cursor
4126 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4127 this.color, this.style);
4129 case 2: // Erase whole line
4130 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
4131 this.color, this.style);
4139 VT100.prototype.csiL = function(number) {
4140 // Open line by inserting blank line(s)
4141 if (this.cursorY >= this.bottom) {
4147 if (number > this.bottom - this.cursorY) {
4148 number = this.bottom - this.cursorY;
4150 this.scrollRegion(0, this.cursorY,
4151 this.terminalWidth, this.bottom - this.cursorY - number,
4152 0, number, this.color, this.style);
4156 VT100.prototype.csiM = function(number) {
4157 // Delete line(s), scrolling up the bottom of the screen.
4158 if (this.cursorY >= this.bottom) {
4164 if (number > this.bottom - this.cursorY) {
4165 number = bottom - cursorY;
4167 this.scrollRegion(0, this.cursorY + number,
4168 this.terminalWidth, this.bottom - this.cursorY - number,
4169 0, -number, this.color, this.style);
4173 VT100.prototype.csim = function() {
4174 for (var i = 0; i <= this.npar; i++) {
4175 switch (this.par[i]) {
4176 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
4177 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
4178 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
4179 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
4180 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
4181 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
4183 this.translate = this.GMap[this.useGMap];
4184 this.dispCtrl = false;
4185 this.toggleMeta = false;
4188 this.translate = this.CodePage437Map;
4189 this.dispCtrl = true;
4190 this.toggleMeta = false;
4193 this.translate = this.CodePage437Map;
4194 this.dispCtrl = true;
4195 this.toggleMeta = true;
4198 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
4199 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
4200 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
4201 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
4202 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
4203 0x0200 /* ATTR_UNDERLINE */; break;
4204 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
4205 case 49: this.attr |= 0xF0; break;
4207 if (this.par[i] >= 30 && this.par[i] <= 37) {
4208 var fg = this.par[i] - 30;
4209 this.attr = (this.attr & ~0x0F) | fg;
4210 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
4211 var bg = this.par[i] - 40;
4212 this.attr = (this.attr & ~0xF0) | (bg << 4);
4220 VT100.prototype.csiP = function(number) {
4221 // Delete character(s) following cursor
4225 if (number > this.terminalWidth - this.cursorX) {
4226 number = this.terminalWidth - this.cursorX;
4228 this.scrollRegion(this.cursorX + number, this.cursorY,
4229 this.terminalWidth - this.cursorX - number, 1,
4230 -number, 0, this.color, this.style);
4234 VT100.prototype.csiX = function(number) {
4235 // Clear characters following cursor
4239 if (number > this.terminalWidth - this.cursorX) {
4240 number = this.terminalWidth - this.cursorX;
4242 this.clearRegion(this.cursorX, this.cursorY, number, 1,
4243 this.color, this.style);
4247 VT100.prototype.settermCommand = function() {
4248 // Setterm commands are not implemented
4251 VT100.prototype.doControl = function(ch) {
4252 if (this.printing) {
4253 this.sendControlToPrinter(ch);
4258 case 0x00: /* ignored */ break;
4259 case 0x08: this.bs(); break;
4260 case 0x09: this.ht(); break;
4264 case 0x84: this.lf(); if (!this.crLfMode) break;
4265 case 0x0D: this.cr(); break;
4266 case 0x85: this.cr(); this.lf(); break;
4267 case 0x0E: this.useGMap = 1;
4268 this.translate = this.GMap[1];
4269 this.dispCtrl = true; break;
4270 case 0x0F: this.useGMap = 0;
4271 this.translate = this.GMap[0];
4272 this.dispCtrl = false; break;
4274 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
4275 case 0x1B: this.isEsc = 1 /* ESesc */; break;
4276 case 0x7F: /* ignored */ break;
4277 case 0x88: this.userTabStop[this.cursorX] = true; break;
4278 case 0x8D: this.ri(); break;
4279 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
4280 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
4281 case 0x9A: this.respondID(); break;
4282 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
4283 case 0x07: if (this.isEsc != 17 /* EStitle */) {
4287 default: switch (this.isEsc) {
4289 this.isEsc = 0 /* ESnormal */;
4291 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
4292 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
4294 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
4296 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
4298 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
4299 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
4300 /*7*/ case 0x37: this.saveCursor(); break;
4301 /*8*/ case 0x38: this.restoreCursor(); break;
4302 /*>*/ case 0x3E: this.applKeyMode = false; break;
4303 /*=*/ case 0x3D: this.applKeyMode = true; break;
4304 /*D*/ case 0x44: this.lf(); break;
4305 /*E*/ case 0x45: this.cr(); this.lf(); break;
4306 /*M*/ case 0x4D: this.ri(); break;
4307 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
4308 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
4309 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
4310 /*Z*/ case 0x5A: this.respondID(); break;
4311 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
4312 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
4313 /*c*/ case 0x63: this.reset(); break;
4314 /*g*/ case 0x67: this.flashScreen(); break;
4318 case 15 /* ESnonstd */:
4322 /*2*/ case 0x32: this.isEsc = 17 /* EStitle */; this.titleString = ''; break;
4323 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4324 this.isEsc = 16 /* ESpalette */; break;
4325 /*R*/ case 0x52: // Palette support is not implemented
4326 this.isEsc = 0 /* ESnormal */; break;
4327 default: this.isEsc = 0 /* ESnormal */; break;
4330 case 16 /* ESpalette */:
4331 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4332 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4333 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4334 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
4336 if (this.npar == 7) {
4337 // Palette support is not implemented
4338 this.isEsc = 0 /* ESnormal */;
4341 this.isEsc = 0 /* ESnormal */;
4344 case 2 /* ESsquare */:
4346 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
4347 0, 0, 0, 0, 0, 0, 0, 0 ];
4348 this.isEsc = 3 /* ESgetpars */;
4349 /*[*/ if (ch == 0x5B) { // Function key
4350 this.isEsc = 6 /* ESfunckey */;
4353 /*?*/ this.isQuestionMark = ch == 0x3F;
4354 if (this.isQuestionMark) {
4359 case 5 /* ESdeviceattr */:
4360 case 3 /* ESgetpars */:
4361 /*;*/ if (ch == 0x3B) {
4364 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4365 var par = this.par[this.npar];
4366 if (par == undefined) {
4369 this.par[this.npar] = 10*par + (ch & 0xF);
4371 } else if (this.isEsc == 5 /* ESdeviceattr */) {
4373 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
4374 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
4375 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
4376 /*p*/ case 0x70: /* set pointer mode resource value */ break;
4379 this.isEsc = 0 /* ESnormal */;
4382 this.isEsc = 4 /* ESgotpars */;
4385 case 4 /* ESgotpars */:
4386 this.isEsc = 0 /* ESnormal */;
4387 if (this.isQuestionMark) {
4389 /*h*/ case 0x68: this.setMode(true); break;
4390 /*l*/ case 0x6C: this.setMode(false); break;
4391 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
4394 this.isQuestionMark = false;
4398 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
4399 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
4401 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
4402 /*A*/ case 0x41: this.gotoXY(this.cursorX,
4403 this.cursorY - (this.par[0] ? this.par[0] : 1));
4406 /*e*/ case 0x65: this.gotoXY(this.cursorX,
4407 this.cursorY + (this.par[0] ? this.par[0] : 1));
4410 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4411 this.cursorY); break;
4412 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4413 this.cursorY); break;
4414 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4416 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4418 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
4420 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
4421 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
4422 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
4423 /*i*/ case 0x69: this.csii(this.par[0]); break;
4424 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
4425 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
4426 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
4427 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
4428 /*m*/ case 0x6D: this.csim(); break;
4429 /*P*/ case 0x50: this.csiP(this.par[0]); break;
4430 /*X*/ case 0x58: this.csiX(this.par[0]); break;
4431 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4432 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4433 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4434 /*g*/ case 0x67: if (this.par[0] == 0) {
4435 this.userTabStop[this.cursorX] = false;
4436 } else if (this.par[0] == 2 || this.par[0] == 3) {
4437 this.userTabStop = [ ];
4438 for (var i = 0; i < this.terminalWidth; i++) {
4439 this.userTabStop[i] = false;
4443 /*h*/ case 0x68: this.setMode(true); break;
4444 /*l*/ case 0x6C: this.setMode(false); break;
4445 /*n*/ case 0x6E: switch (this.par[0]) {
4446 case 5: this.statusReport(); break;
4447 case 6: this.cursorReport(); break;
4451 /*q*/ case 0x71: // LED control not implemented
4453 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4454 var b = this.par[1] ? this.par[1]
4455 : this.terminalHeight;
4456 if (t < b && b <= this.terminalHeight) {
4462 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4463 if (c > this.terminalWidth * this.terminalHeight) {
4464 c = this.terminalWidth * this.terminalHeight;
4467 lineBuf += this.lastCharacter;
4470 /*s*/ case 0x73: this.saveCursor(); break;
4471 /*u*/ case 0x75: this.restoreCursor(); break;
4472 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4473 /*]*/ case 0x5D: this.settermCommand(); break;
4477 case 12 /* ESbang */:
4481 this.isEsc = 0 /* ESnormal */;
4483 case 13 /* ESpercent */:
4484 this.isEsc = 0 /* ESnormal */;
4486 /*@*/ case 0x40: this.utfEnabled = false; break;
4488 /*8*/ case 0x38: this.utfEnabled = true; break;
4492 case 6 /* ESfunckey */:
4493 this.isEsc = 0 /* ESnormal */; break;
4494 case 7 /* EShash */:
4495 this.isEsc = 0 /* ESnormal */;
4496 /*8*/ if (ch == 0x38) {
4497 // Screen alignment test not implemented
4500 case 8 /* ESsetG0 */:
4501 case 9 /* ESsetG1 */:
4502 case 10 /* ESsetG2 */:
4503 case 11 /* ESsetG3 */:
4504 var g = this.isEsc - 8 /* ESsetG0 */;
4505 this.isEsc = 0 /* ESnormal */;
4507 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4509 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4510 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4511 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4514 if (this.useGMap == g) {
4515 this.translate = this.GMap[g];
4518 case 17 /* EStitle */:
4520 if (this.titleString && this.titleString.charAt(0) == ';') {
4521 this.titleString = this.titleString.substr(1);
4522 if (this.titleString != '') {
4523 this.titleString += ' - ';
4525 this.titleString += 'Shell In A Box'
4528 window.document.title = this.titleString;
4531 this.isEsc = 0 /* ESnormal */;
4533 this.titleString += String.fromCharCode(ch);
4536 case 18 /* ESss2 */:
4537 case 19 /* ESss3 */:
4539 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4540 [this.toggleMeta ? (ch | 0x80) : ch];
4541 if ((ch & 0xFF00) == 0xF000) {
4543 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4544 this.isEsc = 0 /* ESnormal */; break;
4547 this.lastCharacter = String.fromCharCode(ch);
4548 lineBuf += this.lastCharacter;
4549 this.isEsc = 0 /* ESnormal */; break;
4551 this.isEsc = 0 /* ESnormal */; break;
4558 VT100.prototype.renderString = function(s, showCursor) {
4559 if (this.printing) {
4560 this.sendToPrinter(s);
4567 // We try to minimize the number of DOM operations by coalescing individual
4568 // characters into strings. This is a significant performance improvement.
4569 var incX = s.length;
4570 if (incX > this.terminalWidth - this.cursorX) {
4571 incX = this.terminalWidth - this.cursorX;
4575 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4578 // Minimize the number of calls to putString(), by avoiding a direct
4579 // call to this.showCursor()
4580 this.cursor.style.visibility = '';
4582 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4585 VT100.prototype.vt100 = function(s) {
4586 this.cursorNeedsShowing = this.hideCursor();
4587 this.respondString = '';
4589 for (var i = 0; i < s.length; i++) {
4590 var ch = s.charCodeAt(i);
4591 if (this.utfEnabled) {
4592 // Decode UTF8 encoded character
4594 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4595 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4596 if (--this.utfCount <= 0) {
4597 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4606 if ((ch & 0xE0) == 0xC0) {
4608 this.utfChar = ch & 0x1F;
4609 } else if ((ch & 0xF0) == 0xE0) {
4611 this.utfChar = ch & 0x0F;
4612 } else if ((ch & 0xF8) == 0xF0) {
4614 this.utfChar = ch & 0x07;
4615 } else if ((ch & 0xFC) == 0xF8) {
4617 this.utfChar = ch & 0x03;
4618 } else if ((ch & 0xFE) == 0xFC) {
4620 this.utfChar = ch & 0x01;
4630 var isNormalCharacter =
4631 (ch >= 32 && ch <= 127 || ch >= 160 ||
4632 this.utfEnabled && ch >= 128 ||
4633 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4634 (ch != 0x7F || this.dispCtrl);
4636 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4638 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4640 if ((ch & 0xFF00) == 0xF000) {
4642 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4645 if (!this.printing) {
4646 if (this.needWrap || this.insertMode) {
4648 this.renderString(lineBuf);
4652 if (this.needWrap) {
4653 this.cr(); this.lf();
4655 if (this.insertMode) {
4656 this.scrollRegion(this.cursorX, this.cursorY,
4657 this.terminalWidth - this.cursorX - 1, 1,
4658 1, 0, this.color, this.style);
4661 this.lastCharacter = String.fromCharCode(ch);
4662 lineBuf += this.lastCharacter;
4663 if (!this.printing &&
4664 this.cursorX + lineBuf.length >= this.terminalWidth) {
4665 this.needWrap = this.autoWrapMode;
4669 this.renderString(lineBuf);
4672 var expand = this.doControl(ch);
4673 if (expand.length) {
4674 var r = this.respondString;
4675 this.respondString= r + this.vt100(expand);
4680 this.renderString(lineBuf, this.cursorNeedsShowing);
4681 } else if (this.cursorNeedsShowing) {
4684 return this.respondString;
4687 VT100.prototype.Latin1Map = [
4688 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4689 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4690 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4691 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4692 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4693 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4694 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4695 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4696 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4697 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4698 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4699 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4700 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4701 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4702 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4703 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4704 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4705 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4706 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4707 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4708 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4709 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4710 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4711 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4712 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4713 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4714 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4715 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4716 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4717 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4718 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4719 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4722 VT100.prototype.VT100GraphicsMap = [
4723 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4724 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4725 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4726 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4727 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4728 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4729 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4730 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4731 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4732 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4733 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4734 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4735 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4736 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4737 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4738 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4739 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4740 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4741 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4742 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4743 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4744 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4745 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4746 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4747 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4748 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4749 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4750 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4751 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4752 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4753 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4754 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4757 VT100.prototype.CodePage437Map = [
4758 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4759 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4760 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4761 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4762 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4763 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4764 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4765 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4766 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4767 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4768 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4769 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4770 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4771 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4772 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4773 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4774 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4775 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4776 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4777 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4778 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4779 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4780 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4781 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4782 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4783 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4784 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4785 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4786 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4787 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4788 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4789 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4792 VT100.prototype.DirectToFontMap = [
4793 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4794 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4795 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4796 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4797 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4798 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4799 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4800 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4801 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4802 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4803 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4804 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4805 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4806 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4807 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4808 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4809 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4810 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4811 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4812 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4813 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4814 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4815 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4816 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4817 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4818 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4819 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4820 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4821 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4822 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4823 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4824 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4827 VT100.prototype.ctrlAction = [
4828 true, false, false, false, false, false, false, true,
4829 true, true, true, true, true, true, true, true,
4830 false, false, false, false, false, false, false, false,
4831 true, false, true, true, false, false, false, false
4834 VT100.prototype.ctrlAlways = [
4835 true, false, false, false, false, false, false, false,
4836 true, false, true, false, true, true, true, true,
4837 false, false, false, false, false, false, false, false,
4838 false, false, false, true, false, false, false, false