1 // This file contains code from shell_in_a_box.js and vt100.js
4 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
5 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2 as
9 // published by the Free Software Foundation.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // In addition to these license terms, the author grants the following
23 // If you modify this program, or any covered work, by linking or
24 // combining it with the OpenSSL project's OpenSSL library (or a
25 // modified version of that library), containing parts covered by the
26 // terms of the OpenSSL or SSLeay licenses, the author
27 // grants you additional permission to convey the resulting work.
28 // Corresponding Source for a non-source form of such a combination
29 // shall include the source code for the parts of OpenSSL used as well
30 // as that of the covered work.
32 // You may at your option choose to remove this additional permission from
33 // the work, or from any part of it.
35 // It is possible to build this program in a way that it loads OpenSSL
36 // libraries at run-time. If doing so, the following notices are required
37 // by the OpenSSL and SSLeay licenses:
39 // This product includes software developed by the OpenSSL Project
40 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
42 // This product includes cryptographic software written by Eric Young
43 // (eay@cryptsoft.com)
46 // The most up-to-date version of this program is always available from
47 // http://shellinabox.com
52 // The author believes that for the purposes of this license, you meet the
53 // requirements for publishing the source code, if your web server publishes
54 // the source in unmodified form (i.e. with licensing information, comments,
55 // formatting, and identifier names intact). If there are technical reasons
56 // that require you to make changes to the source code when serving the
57 // JavaScript (e.g to remove pre-processor directives from the source), these
58 // changes should be done in a reversible fashion.
60 // The author does not consider websites that reference this script in
61 // unmodified form, and web servers that serve this script in unmodified form
62 // to be derived works. As such, they are believed to be outside of the
63 // scope of this license and not subject to the rights or restrictions of the
64 // GNU General Public License.
66 // If in doubt, consult a legal professional familiar with the laws that
67 // apply in your country.
69 // #define XHR_UNITIALIZED 0
72 // #define XHR_RECEIVING 3
73 // #define XHR_LOADED 4
75 // IE does not define XMLHttpRequest by default, so we provide a suitable
77 if (typeof XMLHttpRequest == 'undefined') {
78 XMLHttpRequest = function() {
79 try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
80 try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
81 try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { }
82 try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
87 function extend(subClass, baseClass) {
88 function inheritance() { }
89 inheritance.prototype = baseClass.prototype;
90 subClass.prototype = new inheritance();
91 subClass.prototype.constructor = subClass;
92 subClass.prototype.superClass = baseClass.prototype;
95 function ShellInABox(url, container) {
96 if (url == undefined) {
97 this.rooturl = document.location.href;
98 this.url = document.location.href.replace(/[?#].*/, '');
103 if (document.location.hash != '') {
104 var hash = decodeURIComponent(document.location.hash).
106 this.nextUrl = hash.replace(/,.*/, '');
107 this.session = hash.replace(/[^,]*,/, '');
109 this.nextUrl = this.url;
112 this.pendingKeys = '';
113 this.keysInFlight = false;
114 this.connected = false;
115 this.superClass.constructor.call(this, container);
117 // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
118 // Chrome never realizes that the page has loaded.
119 setTimeout(function(shellInABox) {
121 shellInABox.sendRequest();
125 extend(ShellInABox, VT100);
127 ShellInABox.prototype.sessionClosed = function() {
129 this.connected = false;
131 this.session = undefined;
132 if (this.cursorX > 0) {
135 this.vt100('Session closed.');
137 this.showReconnect(true);
142 ShellInABox.prototype.reconnect = function() {
143 this.showReconnect(false);
145 if (document.location.hash != '') {
146 // A shellinaboxd daemon launched from a CGI only allows a single
147 // session. In order to reconnect, we must reload the frame definition
148 // and obtain a new port number. As this is a different origin, we
149 // need to get enclosing page to help us.
150 parent.location = this.nextUrl;
152 if (this.url != this.nextUrl) {
153 document.location.replace(this.nextUrl);
155 this.pendingKeys = '';
156 this.keysInFlight = false;
165 ShellInABox.prototype.sendRequest = function(request) {
166 if (request == undefined) {
167 request = new XMLHttpRequest();
169 request.open('POST', this.url + '?', true);
170 request.setRequestHeader('Cache-Control', 'no-cache');
171 request.setRequestHeader('Content-Type',
172 'application/x-www-form-urlencoded; charset=utf-8');
173 var content = 'width=' + this.terminalWidth +
174 '&height=' + this.terminalHeight +
175 (this.session ? '&session=' +
176 encodeURIComponent(this.session) : '&rooturl='+
177 encodeURIComponent(this.rooturl));
178 request.setRequestHeader('Content-Length', content.length);
180 request.onreadystatechange = function(shellInABox) {
183 return shellInABox.onReadyStateChange(request);
185 shellInABox.sessionClosed();
189 request.send(content);
192 ShellInABox.prototype.onReadyStateChange = function(request) {
193 if (request.readyState == 4 /* XHR_LOADED */) {
194 if (request.status == 200) {
195 this.connected = true;
196 var response = eval('(' + request.responseText + ')');
198 this.vt100(response.data);
201 if (!response.session ||
202 this.session && this.session != response.session) {
203 this.sessionClosed();
205 this.session = response.session;
206 this.sendRequest(request);
208 } else if (request.status == 0) {
210 this.sendRequest(request);
212 this.sessionClosed();
217 ShellInABox.prototype.sendKeys = function(keys) {
218 if (!this.connected) {
221 if (this.keysInFlight || this.session == undefined) {
222 this.pendingKeys += keys;
224 this.keysInFlight = true;
225 keys = this.pendingKeys + keys;
226 this.pendingKeys = '';
227 var request = new XMLHttpRequest();
228 request.open('POST', this.url + '?', true);
229 request.setRequestHeader('Cache-Control', 'no-cache');
230 request.setRequestHeader('Content-Type',
231 'application/x-www-form-urlencoded; charset=utf-8');
232 var content = 'width=' + this.terminalWidth +
233 '&height=' + this.terminalHeight +
234 '&session=' +encodeURIComponent(this.session)+
235 '&keys=' + encodeURIComponent(keys);
236 request.setRequestHeader('Content-Length', content.length);
237 request.onreadystatechange = function(shellInABox) {
240 return shellInABox.keyPressReadyStateChange(request);
245 request.send(content);
249 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
250 if (request.readyState == 4 /* XHR_LOADED */) {
251 this.keysInFlight = false;
252 if (this.pendingKeys) {
258 ShellInABox.prototype.keysPressed = function(ch) {
259 var hex = '0123456789ABCDEF';
261 for (var i = 0; i < ch.length; i++) {
262 var c = ch.charCodeAt(i);
264 s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
265 } else if (c < 0x800) {
266 s += hex.charAt(0xC + (c >> 10) ) +
267 hex.charAt( (c >> 6) & 0xF ) +
268 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
269 hex.charAt( c & 0xF );
270 } else if (c < 0x10000) {
272 hex.charAt( (c >> 12) ) +
273 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
274 hex.charAt( (c >> 6) & 0xF ) +
275 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
276 hex.charAt( c & 0xF );
277 } else if (c < 0x110000) {
279 hex.charAt( (c >> 18) ) +
280 hex.charAt(0x8 + ((c >> 16) & 0x3)) +
281 hex.charAt( (c >> 12) & 0xF ) +
282 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
283 hex.charAt( (c >> 6) & 0xF ) +
284 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
285 hex.charAt( c & 0xF );
291 ShellInABox.prototype.resized = function(w, h) {
292 // Do not send a resize request until we are fully initialized.
294 // sendKeys() always transmits the current terminal size. So, flush all
300 ShellInABox.prototype.toggleSSL = function() {
301 if (document.location.hash != '') {
302 if (this.nextUrl.match(/\?plain$/)) {
303 this.nextUrl = this.nextUrl.replace(/\?plain$/, '');
305 this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain';
308 parent.location = this.nextUrl;
311 this.nextUrl = this.nextUrl.match(/^https:/)
312 ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
313 : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
315 if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
318 if (this.session && this.nextUrl != this.url) {
319 alert('This change will take effect the next time you login.');
323 ShellInABox.prototype.extendContextMenu = function(entries, actions) {
324 // Modify the entries and actions in place, adding any locally defined
326 var oldActions = [ ];
327 for (var i = 0; i < actions.length; i++) {
328 oldActions[i] = actions[i];
330 for (var node = entries.firstChild, i = 0, j = 0; node;
331 node = node.nextSibling) {
332 if (node.tagName == 'LI') {
333 actions[i++] = oldActions[j++];
334 if (node.id == "endconfig") {
336 if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
337 !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
338 // If the server supports both SSL and plain text connections,
339 // provide a menu entry to switch between the two.
340 var newNode = document.createElement('li');
342 if (document.location.hash != '') {
343 isSecure = !this.nextUrl.match(/\?plain$/);
345 isSecure = this.nextUrl.match(/^https:/);
347 newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
348 if (node.nextSibling) {
349 entries.insertBefore(newNode, node.nextSibling);
351 entries.appendChild(newNode);
353 actions[i++] = this.toggleSSL;
356 node.id = 'endconfig';
363 ShellInABox.prototype.about = function() {
364 alert("Shell In A Box version " + "2.10 (revision 239)" +
365 "\nCopyright 2008-2010 by Markus Gutschke\n" +
366 "For more information check http://shellinabox.com" +
367 (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
369 "This product includes software developed by the OpenSSL Project\n" +
370 "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" +
372 "This product includes cryptographic software written by " +
373 "Eric Young\n(eay@cryptsoft.com)" :
378 // VT100.js -- JavaScript based terminal emulator
379 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
381 // This program is free software; you can redistribute it and/or modify
382 // it under the terms of the GNU General Public License version 2 as
383 // published by the Free Software Foundation.
385 // This program is distributed in the hope that it will be useful,
386 // but WITHOUT ANY WARRANTY; without even the implied warranty of
387 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
388 // GNU General Public License for more details.
390 // You should have received a copy of the GNU General Public License along
391 // with this program; if not, write to the Free Software Foundation, Inc.,
392 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
394 // In addition to these license terms, the author grants the following
395 // additional rights:
397 // If you modify this program, or any covered work, by linking or
398 // combining it with the OpenSSL project's OpenSSL library (or a
399 // modified version of that library), containing parts covered by the
400 // terms of the OpenSSL or SSLeay licenses, the author
401 // grants you additional permission to convey the resulting work.
402 // Corresponding Source for a non-source form of such a combination
403 // shall include the source code for the parts of OpenSSL used as well
404 // as that of the covered work.
406 // You may at your option choose to remove this additional permission from
407 // the work, or from any part of it.
409 // It is possible to build this program in a way that it loads OpenSSL
410 // libraries at run-time. If doing so, the following notices are required
411 // by the OpenSSL and SSLeay licenses:
413 // This product includes software developed by the OpenSSL Project
414 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
416 // This product includes cryptographic software written by Eric Young
417 // (eay@cryptsoft.com)
420 // The most up-to-date version of this program is always available from
421 // http://shellinabox.com
426 // The author believes that for the purposes of this license, you meet the
427 // requirements for publishing the source code, if your web server publishes
428 // the source in unmodified form (i.e. with licensing information, comments,
429 // formatting, and identifier names intact). If there are technical reasons
430 // that require you to make changes to the source code when serving the
431 // JavaScript (e.g to remove pre-processor directives from the source), these
432 // changes should be done in a reversible fashion.
434 // The author does not consider websites that reference this script in
435 // unmodified form, and web servers that serve this script in unmodified form
436 // to be derived works. As such, they are believed to be outside of the
437 // scope of this license and not subject to the rights or restrictions of the
438 // GNU General Public License.
440 // If in doubt, consult a legal professional familiar with the laws that
441 // apply in your country.
443 // #define ESnormal 0
445 // #define ESsquare 2
446 // #define ESgetpars 3
447 // #define ESgotpars 4
448 // #define ESdeviceattr 5
449 // #define ESfunckey 6
453 // #define ESsetG2 10
454 // #define ESsetG3 11
456 // #define ESpercent 13
457 // #define ESignore 14
458 // #define ESnonstd 15
459 // #define ESpalette 16
460 // #define EStitle 17
464 // #define ATTR_DEFAULT 0x00F0
465 // #define ATTR_REVERSE 0x0100
466 // #define ATTR_UNDERLINE 0x0200
467 // #define ATTR_DIM 0x0400
468 // #define ATTR_BRIGHT 0x0800
469 // #define ATTR_BLINK 0x1000
471 // #define MOUSE_DOWN 0
472 // #define MOUSE_UP 1
473 // #define MOUSE_CLICK 2
475 function VT100(container) {
476 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
479 this.urlRE = new RegExp(
480 // Known URL protocol are "http", "https", and "ftp".
481 '(?:http|https|ftp)://' +
483 // Optionally allow username and passwords.
484 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
487 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
488 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
489 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
492 '(?::[1-9][0-9]*)?' +
495 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
497 (linkifyURLs <= 1 ? '' :
498 // Also support URLs without a protocol (assume "http").
499 // Optional username and password.
500 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
502 // Hostnames must end with a well-known top-level domain or must be
504 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
507 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
508 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
509 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
510 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
511 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
512 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
513 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
514 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
515 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
516 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
517 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
518 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
519 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
520 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
523 '(?::[1-9][0-9]{0,4})?' +
526 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
528 // In addition, support e-mail address. Optionally, recognize "mailto:"
529 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
532 '[-_.+a-zA-Z0-9]+@' +
535 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
536 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
537 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
538 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
539 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
540 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
541 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
542 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
543 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
544 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
545 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
546 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
547 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
548 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
550 // Optional arguments
551 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
553 this.getUserSettings();
554 this.initializeElements(container);
555 this.maxScrollbackLines = 500;
558 this.isQuestionMark = false;
561 this.savedAttr = [ ];
562 this.savedUseGMap = 0;
563 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
564 this.CodePage437Map, this.DirectToFontMap ];
565 this.savedValid = [ ];
566 this.respondString = '';
567 this.titleString = '';
568 this.internalClipboard = undefined;
572 VT100.prototype.reset = function(clearHistory) {
573 this.isEsc = 0 /* ESnormal */;
574 this.needWrap = false;
575 this.autoWrapMode = true;
576 this.dispCtrl = false;
577 this.toggleMeta = false;
578 this.insertMode = false;
579 this.applKeyMode = false;
580 this.cursorKeyMode = false;
581 this.crLfMode = false;
582 this.offsetMode = false;
583 this.mouseReporting = false;
584 this.printing = false;
585 if (typeof this.printWin != 'undefined' &&
586 this.printWin && !this.printWin.closed) {
587 this.printWin.close();
589 this.printWin = null;
590 this.utfEnabled = this.utfPreferred;
593 this.color = 'ansi0 bgAnsi15';
595 this.attr = 0x00F0 /* ATTR_DEFAULT */;
597 this.GMap = [ this.Latin1Map,
598 this.VT100GraphicsMap,
600 this.DirectToFontMap];
601 this.translate = this.GMap[this.useGMap];
603 this.bottom = this.terminalHeight;
604 this.lastCharacter = ' ';
605 this.userTabStop = [ ];
608 for (var i = 0; i < 2; i++) {
609 while (this.console[i].firstChild) {
610 this.console[i].removeChild(this.console[i].firstChild);
615 this.enableAlternateScreen(false);
617 var wasCompressed = false;
618 var transform = this.getTransformName();
620 for (var i = 0; i < 2; ++i) {
621 wasCompressed |= this.console[i].style[transform] != '';
622 this.console[i].style[transform] = '';
624 this.cursor.style[transform] = '';
625 this.space.style[transform] = '';
626 if (transform == 'filter') {
627 this.console[this.currentScreen].style.width = '';
637 this.isInverted = false;
638 this.refreshInvertedState();
639 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
640 this.color, this.style);
643 VT100.prototype.addListener = function(elem, event, listener) {
645 if (elem.addEventListener) {
646 elem.addEventListener(event, listener, false);
648 elem.attachEvent('on' + event, listener);
654 VT100.prototype.getUserSettings = function() {
655 // Compute hash signature to identify the entries in the userCSS menu.
656 // If the menu is unchanged from last time, default values can be
657 // looked up in a cookie associated with this page.
659 this.utfPreferred = true;
660 this.visualBell = typeof suppressAllAudio != 'undefined' &&
662 this.autoprint = true;
663 this.softKeyboard = false;
664 this.blinkingCursor = true;
665 if (this.visualBell) {
666 this.signature = Math.floor(16807*this.signature + 1) %
669 if (typeof userCSSList != 'undefined') {
670 for (var i = 0; i < userCSSList.length; ++i) {
671 var label = userCSSList[i][0];
672 for (var j = 0; j < label.length; ++j) {
673 this.signature = Math.floor(16807*this.signature+
674 label.charCodeAt(j)) %
677 if (userCSSList[i][1]) {
678 this.signature = Math.floor(16807*this.signature + 1) %
684 var key = 'shellInABox=' + this.signature + ':';
685 var settings = document.cookie.indexOf(key);
687 settings = document.cookie.substr(settings + key.length).
688 replace(/([0-1]*).*/, "$1");
689 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
690 0 : userCSSList.length)) {
691 this.utfPreferred = settings.charAt(0) != '0';
692 this.visualBell = settings.charAt(1) != '0';
693 this.autoprint = settings.charAt(2) != '0';
694 this.softKeyboard = settings.charAt(3) != '0';
695 this.blinkingCursor = settings.charAt(4) != '0';
696 if (typeof userCSSList != 'undefined') {
697 for (var i = 0; i < userCSSList.length; ++i) {
698 userCSSList[i][2] = settings.charAt(i + 5) != '0';
703 this.utfEnabled = this.utfPreferred;
706 VT100.prototype.storeUserSettings = function() {
707 var settings = 'shellInABox=' + this.signature + ':' +
708 (this.utfEnabled ? '1' : '0') +
709 (this.visualBell ? '1' : '0') +
710 (this.autoprint ? '1' : '0') +
711 (this.softKeyboard ? '1' : '0') +
712 (this.blinkingCursor ? '1' : '0');
713 if (typeof userCSSList != 'undefined') {
714 for (var i = 0; i < userCSSList.length; ++i) {
715 settings += userCSSList[i][2] ? '1' : '0';
719 d.setDate(d.getDate() + 3653);
720 document.cookie = settings + ';expires=' + d.toGMTString();
723 VT100.prototype.initializeUserCSSStyles = function() {
724 this.usercssActions = [];
725 if (typeof userCSSList != 'undefined') {
728 var wasSingleSel = 1;
729 var beginOfGroup = 0;
730 for (var i = 0; i <= userCSSList.length; ++i) {
731 if (i < userCSSList.length) {
732 var label = userCSSList[i][0];
733 var newGroup = userCSSList[i][1];
734 var enabled = userCSSList[i][2];
736 // Add user style sheet to document
737 var style = document.createElement('link');
738 var id = document.createAttribute('id');
739 id.nodeValue = 'usercss-' + i;
740 style.setAttributeNode(id);
741 var rel = document.createAttribute('rel');
742 rel.nodeValue = 'stylesheet';
743 style.setAttributeNode(rel);
744 var href = document.createAttribute('href');
745 href.nodeValue = 'usercss-' + i + '.css';
746 style.setAttributeNode(href);
747 var type = document.createAttribute('type');
748 type.nodeValue = 'text/css';
749 style.setAttributeNode(type);
750 document.getElementsByTagName('head')[0].appendChild(style);
751 style.disabled = !enabled;
755 if (newGroup || i == userCSSList.length) {
756 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
757 // The last group had multiple entries that are mutually exclusive;
758 // or the previous to last group did. In either case, we need to
759 // append a "<hr />" before we can add the last group to the menu.
762 wasSingleSel = i - beginOfGroup < 1;
766 for (var j = beginOfGroup; j < i; ++j) {
767 this.usercssActions[this.usercssActions.length] =
768 function(vt100, current, begin, count) {
770 // Deselect all other entries in the group, then either select
771 // (for multiple entries in group) or toggle (for on/off entry)
772 // the current entry.
774 var entry = vt100.getChildById(vt100.menu,
778 for (var c = count; c > 0; ++j) {
779 if (entry.tagName == 'LI') {
782 var label = vt100.usercss.childNodes[j];
784 // Restore label to just the text content
785 if (typeof label.textContent == 'undefined') {
786 var s = label.innerText;
787 label.innerHTML = '';
788 label.appendChild(document.createTextNode(s));
790 label.textContent= label.textContent;
793 // User style sheets are numbered sequentially
794 var sheet = document.getElementById(
798 sheet.disabled = !sheet.disabled;
800 sheet.disabled = false;
802 if (!sheet.disabled) {
803 label.innerHTML= '<img src="/webshell/enabled.gif" />' +
807 sheet.disabled = true;
809 userCSSList[i][2] = !sheet.disabled;
812 entry = entry.nextSibling;
815 // If the font size changed, adjust cursor and line dimensions
816 this.cursor.style.cssText= '';
817 this.cursorWidth = this.cursor.clientWidth;
818 this.cursorHeight = this.lineheight.clientHeight;
819 for (i = 0; i < this.console.length; ++i) {
820 for (var line = this.console[i].firstChild; line;
821 line = line.nextSibling) {
822 line.style.height = this.cursorHeight + 'px';
827 }(this, j, beginOfGroup, i - beginOfGroup);
830 if (i == userCSSList.length) {
836 // Collect all entries in a group, before attaching them to the menu.
837 // This is necessary as we don't know whether this is a group of
838 // mutually exclusive options (which should be separated by "<hr />" on
839 // both ends), or whether this is a on/off toggle, which can be grouped
840 // together with other on/off options.
842 '<li>' + (enabled ? '<img src="/webshell/enabled.gif" />' : '') +
846 this.usercss.innerHTML = menu;
850 VT100.prototype.resetLastSelectedKey = function(e) {
851 var key = this.lastSelectedKey;
856 var position = this.mousePosition(e);
858 // We don't get all the necessary events to reliably reselect a key
859 // if we moved away from it and then back onto it. We approximate the
860 // behavior by remembering the key until either we release the mouse
861 // button (we might never get this event if the mouse has since left
862 // the window), or until we move away too far.
863 var box = this.keyboard.firstChild;
864 if (position[0] < box.offsetLeft + key.offsetWidth ||
865 position[1] < box.offsetTop + key.offsetHeight ||
866 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
867 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
868 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
869 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
870 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
871 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
872 if (this.lastSelectedKey.className) log.console('reset: deselecting');
873 this.lastSelectedKey.className = '';
874 this.lastSelectedKey = undefined;
879 VT100.prototype.showShiftState = function(state) {
880 var style = document.getElementById('shift_state');
882 this.setTextContentRaw(style,
883 '#vt100 #keyboard .shifted {' +
884 'display: inline }' +
885 '#vt100 #keyboard .unshifted {' +
888 this.setTextContentRaw(style, '');
890 var elems = this.keyboard.getElementsByTagName('I');
891 for (var i = 0; i < elems.length; ++i) {
892 if (elems[i].id == '16') {
893 elems[i].className = state ? 'selected' : '';
898 VT100.prototype.showCtrlState = function(state) {
899 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
901 ctrl.className = state ? 'selected' : '';
905 VT100.prototype.showAltState = function(state) {
906 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
908 alt.className = state ? 'selected' : '';
912 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
917 fake.shiftKey = shift;
920 return this.handleKey(fake);
923 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
924 if (elem == undefined) {
927 if (ch == '\u00A0') {
928 // should be treated as a regular space character.
931 if (ch != undefined && CH == undefined) {
932 // For letter keys, we automatically compute the uppercase character code
933 // from the lowercase one.
934 CH = ch.toUpperCase();
936 if (KEY == undefined && key != undefined) {
937 // Most keys have identically key codes for both lowercase and uppercase
938 // keypresses. Normally, only function keys would have distinct key codes,
939 // whereas regular keys have character codes.
941 } else if (KEY == undefined && CH != undefined) {
942 // For regular keys, copy the character code to the key code.
943 KEY = CH.charCodeAt(0);
945 if (key == undefined && ch != undefined) {
946 // For regular keys, copy the character code to the key code.
947 key = ch.charCodeAt(0);
949 // Convert characters to numeric character codes. If the character code
950 // is undefined (i.e. this is a function key), set it to zero.
951 ch = ch ? ch.charCodeAt(0) : 0;
952 CH = CH ? CH.charCodeAt(0) : 0;
954 // Mouse down events high light the key. We also set lastSelectedKey. This
955 // is needed to that mouseout/mouseover can keep track of the key that
956 // is currently being clicked.
957 this.addListener(elem, 'mousedown',
958 function(vt100, elem, key) { return function(e) {
959 if ((e.which || e.button) == 1) {
960 if (vt100.lastSelectedKey) {
961 vt100.lastSelectedKey.className= '';
963 // Highlight the key while the mouse button is held down.
964 if (key == 16 /* Shift */) {
965 if (!elem.className != vt100.isShift) {
966 vt100.showShiftState(!vt100.isShift);
968 } else if (key == 17 /* Ctrl */) {
969 if (!elem.className != vt100.isCtrl) {
970 vt100.showCtrlState(!vt100.isCtrl);
972 } else if (key == 18 /* Alt */) {
973 if (!elem.className != vt100.isAlt) {
974 vt100.showAltState(!vt100.isAlt);
977 elem.className = 'selected';
979 vt100.lastSelectedKey = elem;
981 return false; }; }(this, elem, key));
983 // Modifier keys update the state of the keyboard, but do not generate
984 // any key clicks that get forwarded to the application.
985 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
986 function(vt100, elem) { return function(e) {
987 if (elem == vt100.lastSelectedKey) {
988 if (key == 16 /* Shift */) {
989 // The user clicked the Shift key
990 vt100.isShift = !vt100.isShift;
991 vt100.showShiftState(vt100.isShift);
992 } else if (key == 17 /* Ctrl */) {
993 vt100.isCtrl = !vt100.isCtrl;
994 vt100.showCtrlState(vt100.isCtrl);
995 } else if (key == 18 /* Alt */) {
996 vt100.isAlt = !vt100.isAlt;
997 vt100.showAltState(vt100.isAlt);
999 vt100.lastSelectedKey = undefined;
1001 if (vt100.lastSelectedKey) {
1002 vt100.lastSelectedKey.className = '';
1003 vt100.lastSelectedKey = undefined;
1005 return false; }; }(this, elem) :
1006 // Regular keys generate key clicks, when the mouse button is released or
1007 // when a mouse click event is received.
1008 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
1009 if (vt100.lastSelectedKey) {
1010 if (elem == vt100.lastSelectedKey) {
1011 // The user clicked a key.
1012 if (vt100.isShift) {
1013 vt100.clickedKeyboard(e, elem, CH, KEY,
1014 true, vt100.isCtrl, vt100.isAlt);
1016 vt100.clickedKeyboard(e, elem, ch, key,
1017 false, vt100.isCtrl, vt100.isAlt);
1019 vt100.isShift = false;
1020 vt100.showShiftState(false);
1021 vt100.isCtrl = false;
1022 vt100.showCtrlState(false);
1023 vt100.isAlt = false;
1024 vt100.showAltState(false);
1026 vt100.lastSelectedKey.className = '';
1027 vt100.lastSelectedKey = undefined;
1029 elem.className = '';
1030 return false; }; }(this, elem, ch, key, CH, KEY);
1031 this.addListener(elem, 'mouseup', clicked);
1032 this.addListener(elem, 'click', clicked);
1034 // When moving the mouse away from a key, check if any keys need to be
1036 this.addListener(elem, 'mouseout',
1037 function(vt100, elem, key) { return function(e) {
1038 if (key == 16 /* Shift */) {
1039 if (!elem.className == vt100.isShift) {
1040 vt100.showShiftState(vt100.isShift);
1042 } else if (key == 17 /* Ctrl */) {
1043 if (!elem.className == vt100.isCtrl) {
1044 vt100.showCtrlState(vt100.isCtrl);
1046 } else if (key == 18 /* Alt */) {
1047 if (!elem.className == vt100.isAlt) {
1048 vt100.showAltState(vt100.isAlt);
1050 } else if (elem.className) {
1051 elem.className = '';
1052 vt100.lastSelectedKey = elem;
1053 } else if (vt100.lastSelectedKey) {
1054 vt100.resetLastSelectedKey(e);
1056 return false; }; }(this, elem, key));
1058 // When moving the mouse over a key, select it if the user is still holding
1059 // the mouse button down (i.e. elem == lastSelectedKey)
1060 this.addListener(elem, 'mouseover',
1061 function(vt100, elem, key) { return function(e) {
1062 if (elem == vt100.lastSelectedKey) {
1063 if (key == 16 /* Shift */) {
1064 if (!elem.className != vt100.isShift) {
1065 vt100.showShiftState(!vt100.isShift);
1067 } else if (key == 17 /* Ctrl */) {
1068 if (!elem.className != vt100.isCtrl) {
1069 vt100.showCtrlState(!vt100.isCtrl);
1071 } else if (key == 18 /* Alt */) {
1072 if (!elem.className != vt100.isAlt) {
1073 vt100.showAltState(!vt100.isAlt);
1075 } else if (!elem.className) {
1076 elem.className = 'selected';
1079 vt100.resetLastSelectedKey(e);
1081 return false; }; }(this, elem, key));
1084 VT100.prototype.initializeKeyBindings = function(elem) {
1086 if (elem.nodeName == "I" || elem.nodeName == "B") {
1088 // Function keys. The Javascript keycode is part of the "id"
1089 var i = parseInt(elem.id);
1091 // If the id does not parse as a number, it is not a keycode.
1092 this.addKeyBinding(elem, undefined, i);
1095 var child = elem.firstChild;
1097 if (child.nodeName == "#text") {
1098 // If the key only has a text node as a child, then it is a letter.
1099 // Automatically compute the lower and upper case version of the
1101 var text = this.getTextContent(child) ||
1102 this.getTextContent(elem);
1103 this.addKeyBinding(elem, text.toLowerCase());
1104 } else if (child.nextSibling) {
1105 // If the key has two children, they are the lower and upper case
1106 // character code, respectively.
1107 this.addKeyBinding(elem, this.getTextContent(child), undefined,
1108 this.getTextContent(child.nextSibling));
1114 // Recursively parse all other child nodes.
1115 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
1116 this.initializeKeyBindings(elem);
1120 VT100.prototype.initializeKeyboardButton = function() {
1121 // Configure mouse event handlers for button that displays/hides keyboard
1122 this.addListener(this.keyboardImage, 'click',
1123 function(vt100) { return function(e) {
1124 if (vt100.keyboard.style.display != '') {
1125 if (vt100.reconnectBtn.style.visibility != '') {
1126 vt100.initializeKeyboard();
1127 vt100.showSoftKeyboard();
1130 vt100.hideSoftKeyboard();
1131 vt100.input.focus();
1133 return false; }; }(this));
1135 // Enable button that displays keyboard
1136 if (this.softKeyboard) {
1137 this.keyboardImage.style.visibility = 'visible';
1141 VT100.prototype.initializeKeyboard = function() {
1142 // Only need to initialize the keyboard the very first time. When doing so,
1143 // copy the keyboard layout from the iframe.
1144 if (this.keyboard.firstChild) {
1147 this.keyboard.innerHTML =
1148 this.layout.contentDocument.body.innerHTML;
1149 var box = this.keyboard.firstChild;
1150 this.hideSoftKeyboard();
1152 // Configure mouse event handlers for on-screen keyboard
1153 this.addListener(this.keyboard, 'click',
1154 function(vt100) { return function(e) {
1155 vt100.hideSoftKeyboard();
1156 vt100.input.focus();
1157 return false; }; }(this));
1158 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
1159 this.addListener(box, 'click', this.cancelEvent);
1160 this.addListener(box, 'mouseup',
1161 function(vt100) { return function(e) {
1162 if (vt100.lastSelectedKey) {
1163 vt100.lastSelectedKey.className = '';
1164 vt100.lastSelectedKey = undefined;
1166 return false; }; }(this));
1167 this.addListener(box, 'mouseout',
1168 function(vt100) { return function(e) {
1169 return vt100.resetLastSelectedKey(e); }; }(this));
1170 this.addListener(box, 'mouseover',
1171 function(vt100) { return function(e) {
1172 return vt100.resetLastSelectedKey(e); }; }(this));
1174 // Configure SHIFT key behavior
1175 var style = document.createElement('style');
1176 var id = document.createAttribute('id');
1177 id.nodeValue = 'shift_state';
1178 style.setAttributeNode(id);
1179 var type = document.createAttribute('type');
1180 type.nodeValue = 'text/css';
1181 style.setAttributeNode(type);
1182 document.getElementsByTagName('head')[0].appendChild(style);
1184 // Set up key bindings
1185 this.initializeKeyBindings(box);
1188 VT100.prototype.initializeElements = function(container) {
1189 // If the necessary objects have not already been defined in the HTML
1190 // page, create them now.
1192 this.container = container;
1193 } else if (!(this.container = document.getElementById('vt100'))) {
1194 this.container = document.createElement('div');
1195 this.container.id = 'vt100';
1196 document.body.appendChild(this.container);
1199 if (!this.getChildById(this.container, 'reconnect') ||
1200 !this.getChildById(this.container, 'menu') ||
1201 !this.getChildById(this.container, 'keyboard') ||
1202 !this.getChildById(this.container, 'kbd_button') ||
1203 !this.getChildById(this.container, 'kbd_img') ||
1204 !this.getChildById(this.container, 'layout') ||
1205 !this.getChildById(this.container, 'scrollable') ||
1206 !this.getChildById(this.container, 'console') ||
1207 !this.getChildById(this.container, 'alt_console') ||
1208 !this.getChildById(this.container, 'ieprobe') ||
1209 !this.getChildById(this.container, 'padding') ||
1210 !this.getChildById(this.container, 'cursor') ||
1211 !this.getChildById(this.container, 'lineheight') ||
1212 !this.getChildById(this.container, 'usercss') ||
1213 !this.getChildById(this.container, 'space') ||
1214 !this.getChildById(this.container, 'input') ||
1215 !this.getChildById(this.container, 'cliphelper')) {
1216 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
1217 // we might get a pointless warning that a suitable plugin is not yet
1218 // installed. If in doubt, we'd rather just stay silent.
1221 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
1223 embed = typeof suppressAllAudio != 'undefined' &&
1224 suppressAllAudio ? "" :
1225 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
1226 'id="beep_embed" ' +
1228 'autostart="false" ' +
1230 'enablejavascript="true" ' +
1231 'type="audio/x-wav" ' +
1234 'style="position:absolute;left:-1000px;top:-1000px" />';
1239 this.container.innerHTML =
1240 '<div id="reconnect" style="visibility: hidden">' +
1241 '<input type="button" value="Connect" ' +
1242 'onsubmit="return false" />' +
1244 '<div id="cursize" style="visibility: hidden">' +
1246 '<div id="menu"></div>' +
1247 '<div id="keyboard" unselectable="on">' +
1249 '<div id="scrollable">' +
1250 '<table id="kbd_button">' +
1251 '<tr><td width="100%"> </td>' +
1252 '<td><img id="kbd_img" src="/webshell/keyboard.png" /></td>' +
1253 '<td> </td></tr>' +
1255 '<pre id="lineheight"> </pre>' +
1256 '<pre id="console">' +
1258 '<div id="ieprobe"><span> </span></div>' +
1260 '<pre id="alt_console" style="display: none"></pre>' +
1261 '<div id="padding"></div>' +
1262 '<pre id="cursor"> </pre>' +
1264 '<div class="hidden">' +
1265 '<div id="usercss"></div>' +
1266 '<pre><div><span id="space"></span></div></pre>' +
1267 '<input type="textfield" id="input" autocorrect="off" autocapitalize="off" />' +
1268 '<input type="textfield" id="cliphelper" />' +
1269 (typeof suppressAllAudio != 'undefined' &&
1270 suppressAllAudio ? "" :
1271 embed + '<bgsound id="beep_bgsound" loop=1 />') +
1272 '<iframe id="layout" src="/webshell/keyboard.html" />' +
1276 // Find the object used for playing the "beep" sound, if any.
1277 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
1278 this.beeper = undefined;
1280 this.beeper = this.getChildById(this.container,
1282 if (!this.beeper || !this.beeper.Play) {
1283 this.beeper = this.getChildById(this.container,
1285 if (!this.beeper || typeof this.beeper.src == 'undefined') {
1286 this.beeper = undefined;
1291 // Initialize the variables for finding the text console and the
1293 this.reconnectBtn = this.getChildById(this.container,'reconnect');
1294 this.curSizeBox = this.getChildById(this.container, 'cursize');
1295 this.menu = this.getChildById(this.container, 'menu');
1296 this.keyboard = this.getChildById(this.container, 'keyboard');
1297 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
1298 this.layout = this.getChildById(this.container, 'layout');
1299 this.scrollable = this.getChildById(this.container,
1301 this.lineheight = this.getChildById(this.container,
1304 [ this.getChildById(this.container, 'console'),
1305 this.getChildById(this.container, 'alt_console') ];
1306 var ieProbe = this.getChildById(this.container, 'ieprobe');
1307 this.padding = this.getChildById(this.container, 'padding');
1308 this.cursor = this.getChildById(this.container, 'cursor');
1309 this.usercss = this.getChildById(this.container, 'usercss');
1310 this.space = this.getChildById(this.container, 'space');
1311 this.input = this.getChildById(this.container, 'input');
1312 this.cliphelper = this.getChildById(this.container,
1315 // Add any user selectable style sheets to the menu
1316 this.initializeUserCSSStyles();
1318 // Remember the dimensions of a standard character glyph. We would
1319 // expect that we could just check cursor.clientWidth/Height at any time,
1320 // but it turns out that browsers sometimes invalidate these values
1321 // (e.g. while displaying a print preview screen).
1322 this.cursorWidth = this.cursor.clientWidth;
1323 this.cursorHeight = this.lineheight.clientHeight;
1325 // IE has a slightly different boxing model, that we need to compensate for
1326 this.isIE = ieProbe.offsetTop > 1;
1327 ieProbe = undefined;
1328 this.console.innerHTML = '';
1330 // Determine if the terminal window is positioned at the beginning of the
1331 // page, or if it is embedded somewhere else in the page. For full-screen
1332 // terminals, automatically resize whenever the browser window changes.
1333 var marginTop = parseInt(this.getCurrentComputedStyle(
1334 document.body, 'marginTop'));
1335 var marginLeft = parseInt(this.getCurrentComputedStyle(
1336 document.body, 'marginLeft'));
1337 var marginRight = parseInt(this.getCurrentComputedStyle(
1338 document.body, 'marginRight'));
1339 var x = this.container.offsetLeft;
1340 var y = this.container.offsetTop;
1341 for (var parent = this.container; parent = parent.offsetParent; ) {
1342 x += parent.offsetLeft;
1343 y += parent.offsetTop;
1345 this.isEmbedded = marginTop != y ||
1347 (window.innerWidth ||
1348 document.documentElement.clientWidth ||
1349 document.body.clientWidth) -
1350 marginRight != x + this.container.offsetWidth;
1351 if (!this.isEmbedded) {
1352 // Some browsers generate resize events when the terminal is first
1353 // shown. Disable showing the size indicator until a little bit after
1354 // the terminal has been rendered the first time.
1355 this.indicateSize = false;
1356 setTimeout(function(vt100) {
1358 vt100.indicateSize = true;
1361 this.addListener(window, 'resize',
1364 vt100.hideContextMenu();
1366 vt100.showCurrentSize();
1370 // Hide extra scrollbars attached to window
1371 document.body.style.margin = '0px';
1372 try { document.body.style.overflow ='hidden'; } catch (e) { }
1373 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1376 // Set up onscreen soft keyboard
1377 this.initializeKeyboardButton();
1379 // Hide context menu
1380 this.hideContextMenu();
1382 // Add listener to reconnect button
1383 this.addListener(this.reconnectBtn.firstChild, 'click',
1386 var rc = vt100.reconnect();
1387 vt100.input.focus();
1392 // Add input listeners
1393 this.addListener(this.input, 'blur',
1395 return function() { vt100.blurCursor(); } }(this));
1396 this.addListener(this.input, 'focus',
1398 return function() { vt100.focusCursor(); } }(this));
1399 this.addListener(this.input, 'keydown',
1401 return function(e) {
1402 if (!e) e = window.event;
1403 return vt100.keyDown(e); } }(this));
1404 this.addListener(this.input, 'keypress',
1406 return function(e) {
1407 if (!e) e = window.event;
1408 return vt100.keyPressed(e); } }(this));
1409 this.addListener(this.input, 'keyup',
1411 return function(e) {
1412 if (!e) e = window.event;
1413 return vt100.keyUp(e); } }(this));
1415 // Attach listeners that move the focus to the <input> field. This way we
1416 // can make sure that we can receive keyboard input.
1417 var mouseEvent = function(vt100, type) {
1418 return function(e) {
1419 if (!e) e = window.event;
1420 return vt100.mouseEvent(e, type);
1423 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1424 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
1425 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
1427 // Check that browser supports drag and drop
1428 if ('draggable' in document.createElement('span')) {
1429 var dropEvent = function (vt100) {
1430 return function(e) {
1431 if (!e) e = window.event;
1432 if (e.preventDefault) e.preventDefault();
1433 vt100.keysPressed(e.dataTransfer.getData('Text'));
1437 // Tell the browser that we *can* drop on this target
1438 this.addListener(this.scrollable, 'dragover', cancel);
1439 this.addListener(this.scrollable, 'dragenter', cancel);
1441 // Add a listener for the drop event
1442 this.addListener(this.scrollable, 'drop', dropEvent(this));
1445 // Initialize the blank terminal window.
1446 this.currentScreen = 0;
1449 this.numScrollbackLines = 0;
1451 this.bottom = 0x7FFFFFFF;
1458 function cancel(event) {
1459 if (event.preventDefault) {
1460 event.preventDefault();
1465 VT100.prototype.getChildById = function(parent, id) {
1466 var nodeList = parent.all || parent.getElementsByTagName('*');
1467 if (typeof nodeList.namedItem == 'undefined') {
1468 for (var i = 0; i < nodeList.length; i++) {
1469 if (nodeList[i].id == id) {
1475 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1476 return elem ? elem[0] || elem : null;
1480 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1481 if (typeof elem.currentStyle != 'undefined') {
1482 return elem.currentStyle[style];
1484 return document.defaultView.getComputedStyle(elem, null)[style];
1488 VT100.prototype.reconnect = function() {
1492 VT100.prototype.showReconnect = function(state) {
1494 this.hideSoftKeyboard();
1495 this.reconnectBtn.style.visibility = '';
1497 this.reconnectBtn.style.visibility = 'hidden';
1501 VT100.prototype.repairElements = function(console) {
1502 for (var line = console.firstChild; line; line = line.nextSibling) {
1503 if (!line.clientHeight) {
1504 var newLine = document.createElement(line.tagName);
1505 newLine.style.cssText = line.style.cssText;
1506 newLine.className = line.className;
1507 if (line.tagName == 'DIV') {
1508 for (var span = line.firstChild; span; span = span.nextSibling) {
1509 var newSpan = document.createElement(span.tagName);
1510 newSpan.style.cssText = span.style.cssText;
1511 newSpan.className = span.className;
1512 this.setTextContent(newSpan, this.getTextContent(span));
1513 newLine.appendChild(newSpan);
1516 this.setTextContent(newLine, this.getTextContent(line));
1518 line.parentNode.replaceChild(newLine, line);
1524 VT100.prototype.resized = function(w, h) {
1527 VT100.prototype.resizer = function() {
1528 // Hide onscreen soft keyboard
1529 this.hideSoftKeyboard();
1531 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1532 // Recreating it, will repair it.
1533 var newCursor = document.createElement('pre');
1534 this.setTextContent(newCursor, ' ');
1535 newCursor.id = 'cursor';
1536 newCursor.style.cssText = this.cursor.style.cssText;
1537 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1538 if (!newCursor.clientHeight) {
1539 // Things are broken right now. This is probably because we are
1540 // displaying the print-preview. Just don't change any of our settings
1541 // until the print dialog is closed again.
1542 newCursor.parentNode.removeChild(newCursor);
1545 // Swap the old broken cursor for the newly created one.
1546 this.cursor.parentNode.removeChild(this.cursor);
1547 this.cursor = newCursor;
1550 // Really horrible things happen if the contents of the terminal changes
1551 // while the print-preview is showing. We get HTML elements that show up
1552 // in the DOM, but that do not take up any space. Find these elements and
1554 this.repairElements(this.console[0]);
1555 this.repairElements(this.console[1]);
1557 // Lock the cursor size to the size of a normal character. This helps with
1558 // characters that are taller/shorter than normal. Unfortunately, we will
1559 // still get confused if somebody enters a character that is wider/narrower
1560 // than normal. This can happen if the browser tries to substitute a
1561 // characters from a different font.
1562 this.cursor.style.width = this.cursorWidth + 'px';
1563 this.cursor.style.height = this.cursorHeight + 'px';
1565 // Adjust height for one pixel padding of the #vt100 element.
1566 // The latter is necessary to properly display the inactive cursor.
1567 var console = this.console[this.currentScreen];
1568 var height = (this.isEmbedded ? this.container.clientHeight
1569 : (window.innerHeight ||
1570 document.documentElement.clientHeight ||
1571 document.body.clientHeight))-1;
1572 var partial = height % this.cursorHeight;
1573 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1574 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1575 var oldTerminalHeight = this.terminalHeight;
1577 this.updateHeight();
1579 // Clip the cursor to the visible screen.
1580 var cx = this.cursorX;
1581 var cy = this.cursorY + this.numScrollbackLines;
1583 // The alternate screen never keeps a scroll back buffer.
1584 this.updateNumScrollbackLines();
1585 while (this.currentScreen && this.numScrollbackLines > 0) {
1586 console.removeChild(console.firstChild);
1587 this.numScrollbackLines--;
1589 cy -= this.numScrollbackLines;
1592 } else if (cx > this.terminalWidth) {
1593 cx = this.terminalWidth - 1;
1600 } else if (cy > this.terminalHeight) {
1601 cy = this.terminalHeight - 1;
1607 // Clip the scroll region to the visible screen.
1608 if (this.bottom > this.terminalHeight ||
1609 this.bottom == oldTerminalHeight) {
1610 this.bottom = this.terminalHeight;
1612 if (this.top >= this.bottom) {
1613 this.top = this.bottom-1;
1619 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1620 // particularly important after changing the screen number), and reset
1621 // the scroll region to the default.
1622 this.truncateLines(this.terminalWidth);
1623 this.putString(cx, cy, '', undefined);
1624 this.scrollable.scrollTop = this.numScrollbackLines *
1625 this.cursorHeight + 1;
1627 // Update classNames for lines in the scrollback buffer
1628 var line = console.firstChild;
1629 for (var i = 0; i < this.numScrollbackLines; i++) {
1630 line.className = 'scrollback';
1631 line = line.nextSibling;
1634 line.className = '';
1635 line = line.nextSibling;
1638 // Reposition the reconnect button
1639 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1641 this.reconnectBtn.clientWidth)/2 + 'px';
1642 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1643 this.reconnectBtn.clientHeight)/2 + 'px';
1645 // Send notification that the window size has been changed
1646 this.resized(this.terminalWidth, this.terminalHeight);
1649 VT100.prototype.showCurrentSize = function() {
1650 if (!this.indicateSize) {
1653 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1654 this.terminalHeight;
1655 this.curSizeBox.style.left =
1656 (this.terminalWidth*this.cursorWidth/
1658 this.curSizeBox.clientWidth)/2 + 'px';
1659 this.curSizeBox.style.top =
1660 (this.terminalHeight*this.cursorHeight -
1661 this.curSizeBox.clientHeight)/2 + 'px';
1662 this.curSizeBox.style.visibility = '';
1663 if (this.curSizeTimeout) {
1664 clearTimeout(this.curSizeTimeout);
1667 // Only show the terminal size for a short amount of time after resizing.
1668 // Then hide this information, again. Some browsers generate resize events
1669 // throughout the entire resize operation. This is nice, and we will show
1670 // the terminal size while the user is dragging the window borders.
1671 // Other browsers only generate a single event when the user releases the
1672 // mouse. In those cases, we can only show the terminal size once at the
1673 // end of the resize operation.
1674 this.curSizeTimeout = setTimeout(function(vt100) {
1676 vt100.curSizeTimeout = null;
1677 vt100.curSizeBox.style.visibility = 'hidden';
1682 VT100.prototype.selection = function() {
1684 return '' + (window.getSelection && window.getSelection() ||
1685 document.selection && document.selection.type == 'Text' &&
1686 document.selection.createRange().text || '');
1692 VT100.prototype.cancelEvent = function(event) {
1694 // For non-IE browsers
1695 event.stopPropagation();
1696 event.preventDefault();
1701 event.cancelBubble = true;
1702 event.returnValue = false;
1710 VT100.prototype.mousePosition = function(event) {
1711 var offsetX = this.container.offsetLeft;
1712 var offsetY = this.container.offsetTop;
1713 for (var e = this.container; e = e.offsetParent; ) {
1714 offsetX += e.offsetLeft;
1715 offsetY += e.offsetTop;
1717 return [ event.clientX - offsetX,
1718 event.clientY - offsetY ];
1721 VT100.prototype.mouseEvent = function(event, type) {
1722 // If any text is currently selected, do not move the focus as that would
1723 // invalidate the selection.
1724 var selection = this.selection();
1725 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1729 // Compute mouse position in characters.
1730 var position = this.mousePosition(event);
1731 var x = Math.floor(position[0] / this.cursorWidth);
1732 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1733 this.cursorHeight) - this.numScrollbackLines;
1735 if (x >= this.terminalWidth) {
1736 x = this.terminalWidth - 1;
1743 if (y >= this.terminalHeight) {
1744 y = this.terminalHeight - 1;
1752 // Compute button number and modifier keys.
1753 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
1754 typeof event.pageX != 'undefined' ? event.button :
1755 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1756 if (button != undefined) {
1757 if (event.shiftKey) {
1760 if (event.altKey || event.metaKey) {
1763 if (event.ctrlKey) {
1768 // Report mouse events if they happen inside of the current screen and
1769 // with the SHIFT key unpressed. Both of these restrictions do not apply
1770 // for button releases, as we always want to report those.
1771 if (this.mouseReporting && !selection.length &&
1772 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1773 if (inside || type != 0 /* MOUSE_DOWN */) {
1774 if (button != undefined) {
1775 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1776 String.fromCharCode(x + 33) +
1777 String.fromCharCode(y + 33);
1778 if (type != 2 /* MOUSE_CLICK */) {
1779 this.keysPressed(report);
1782 // If we reported the event, stop propagating it (not sure, if this
1783 // actually works on most browsers; blocking the global "oncontextmenu"
1784 // even is still necessary).
1785 return this.cancelEvent(event);
1790 // Bring up context menu.
1791 if (button == 2 && !event.shiftKey) {
1792 if (type == 0 /* MOUSE_DOWN */) {
1793 this.showContextMenu(position[0], position[1]);
1795 return this.cancelEvent(event);
1798 if (this.mouseReporting) {
1800 event.shiftKey = false;
1808 VT100.prototype.replaceChar = function(s, ch, repl) {
1809 for (var i = -1;;) {
1810 i = s.indexOf(ch, i + 1);
1814 s = s.substr(0, i) + repl + s.substr(i + 1);
1819 VT100.prototype.htmlEscape = function(s) {
1820 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1821 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1824 VT100.prototype.getTextContent = function(elem) {
1825 return elem.textContent ||
1826 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1829 VT100.prototype.setTextContentRaw = function(elem, s) {
1830 // Updating the content of an element is an expensive operation. It actually
1831 // pays off to first check whether the element is still unchanged.
1832 if (typeof elem.textContent == 'undefined') {
1833 if (elem.innerText != s) {
1837 // Very old versions of IE do not allow setting innerText. Instead,
1838 // remove all children, by setting innerHTML and then set the text
1839 // using DOM methods.
1840 elem.innerHTML = '';
1841 elem.appendChild(document.createTextNode(
1842 this.replaceChar(s, ' ', '\u00A0')));
1846 if (elem.textContent != s) {
1847 elem.textContent = s;
1852 VT100.prototype.setTextContent = function(elem, s) {
1853 // Check if we find any URLs in the text. If so, automatically convert them
1855 if (this.urlRE && this.urlRE.test(s)) {
1859 if (RegExp.leftContext != null) {
1860 inner += this.htmlEscape(RegExp.leftContext);
1861 consumed += RegExp.leftContext.length;
1863 var url = this.htmlEscape(RegExp.lastMatch);
1866 // If no protocol was specified, try to guess a reasonable one.
1867 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1868 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1869 var slash = url.indexOf('/');
1870 var at = url.indexOf('@');
1871 var question = url.indexOf('?');
1873 (at < question || question < 0) &&
1874 (slash < 0 || (question > 0 && slash > question))) {
1875 fullUrl = 'mailto:' + url;
1877 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1882 inner += '<a target="vt100Link" href="' + fullUrl +
1883 '">' + url + '</a>';
1884 consumed += RegExp.lastMatch.length;
1885 s = s.substr(consumed);
1886 if (!this.urlRE.test(s)) {
1887 if (RegExp.rightContext != null) {
1888 inner += this.htmlEscape(RegExp.rightContext);
1893 elem.innerHTML = inner;
1897 this.setTextContentRaw(elem, s);
1900 VT100.prototype.insertBlankLine = function(y, color, style) {
1901 // Insert a blank line a position y. This method ignores the scrollback
1902 // buffer. The caller has to add the length of the scrollback buffer to
1903 // the position, if necessary.
1904 // If the position is larger than the number of current lines, this
1905 // method just adds a new line right after the last existing one. It does
1906 // not add any missing lines in between. It is the caller's responsibility
1909 color = 'ansi0 bgAnsi15';
1915 if (color != 'ansi0 bgAnsi15' && !style) {
1916 line = document.createElement('pre');
1917 this.setTextContent(line, '\n');
1919 line = document.createElement('div');
1920 var span = document.createElement('span');
1921 span.style.cssText = style;
1922 span.className = color;
1923 this.setTextContent(span, this.spaces(this.terminalWidth));
1924 line.appendChild(span);
1926 line.style.height = this.cursorHeight + 'px';
1927 var console = this.console[this.currentScreen];
1928 if (console.childNodes.length > y) {
1929 console.insertBefore(line, console.childNodes[y]);
1931 console.appendChild(line);
1935 VT100.prototype.updateWidth = function() {
1936 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1937 this.cursorWidth*this.scale);
1938 return this.terminalWidth;
1941 VT100.prototype.updateHeight = function() {
1942 // We want to be able to display either a terminal window that fills the
1943 // entire browser window, or a terminal window that is contained in a
1944 // <div> which is embededded somewhere in the web page.
1945 if (this.isEmbedded) {
1946 // Embedded terminal. Use size of the containing <div> (id="vt100").
1947 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1950 // Use the full browser window.
1951 this.terminalHeight = Math.floor(((window.innerHeight ||
1952 document.documentElement.clientHeight ||
1953 document.body.clientHeight)-1)/
1956 return this.terminalHeight;
1959 VT100.prototype.updateNumScrollbackLines = function() {
1960 var scrollback = Math.floor(
1961 this.console[this.currentScreen].offsetHeight /
1962 this.cursorHeight) -
1963 this.terminalHeight;
1964 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1965 return this.numScrollbackLines;
1968 VT100.prototype.truncateLines = function(width) {
1972 for (var line = this.console[this.currentScreen].firstChild; line;
1973 line = line.nextSibling) {
1974 if (line.tagName == 'DIV') {
1977 // Traverse current line and truncate it once we saw "width" characters
1978 for (var span = line.firstChild; span;
1979 span = span.nextSibling) {
1980 var s = this.getTextContent(span);
1982 if (x + l > width) {
1983 this.setTextContent(span, s.substr(0, width - x));
1984 while (span.nextSibling) {
1985 line.removeChild(line.lastChild);
1991 // Prune white space from the end of the current line
1992 var span = line.lastChild;
1994 span.className == 'ansi0 bgAnsi15' &&
1995 !span.style.cssText.length) {
1996 // Scan backwards looking for first non-space character
1997 var s = this.getTextContent(span);
1998 for (var i = s.length; i--; ) {
1999 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2000 if (i+1 != s.length) {
2001 this.setTextContent(s.substr(0, i+1));
2009 span = span.previousSibling;
2011 // Remove blank <span>'s from end of line
2012 line.removeChild(sibling);
2014 // Remove entire line (i.e. <div>), if empty
2015 var blank = document.createElement('pre');
2016 blank.style.height = this.cursorHeight + 'px';
2017 this.setTextContent(blank, '\n');
2018 line.parentNode.replaceChild(blank, line);
2026 VT100.prototype.putString = function(x, y, text, color, style) {
2028 color = 'ansi0 bgAnsi15';
2033 var yIdx = y + this.numScrollbackLines;
2039 var console = this.console[this.currentScreen];
2040 if (!text.length && (yIdx >= console.childNodes.length ||
2041 console.childNodes[yIdx].tagName != 'DIV')) {
2042 // Positioning cursor to a blank location
2045 // Create missing blank lines at end of page
2046 while (console.childNodes.length <= yIdx) {
2047 // In order to simplify lookups, we want to make sure that each line
2048 // is represented by exactly one element (and possibly a whole bunch of
2050 // For non-blank lines, we can create a <div> containing one or more
2051 // <span>s. For blank lines, this fails as browsers tend to optimize them
2052 // away. But fortunately, a <pre> tag containing a newline character
2053 // appears to work for all browsers (a would also work, but then
2054 // copying from the browser window would insert superfluous spaces into
2056 this.insertBlankLine(yIdx);
2058 line = console.childNodes[yIdx];
2060 // If necessary, promote blank '\n' line to a <div> tag
2061 if (line.tagName != 'DIV') {
2062 var div = document.createElement('div');
2063 div.style.height = this.cursorHeight + 'px';
2064 div.innerHTML = '<span></span>';
2065 console.replaceChild(div, line);
2069 // Scan through list of <span>'s until we find the one where our text
2071 span = line.firstChild;
2073 while (span.nextSibling && xPos < x) {
2074 len = this.getTextContent(span).length;
2075 if (xPos + len > x) {
2079 span = span.nextSibling;
2083 // If current <span> is not long enough, pad with spaces or add new
2085 s = this.getTextContent(span);
2086 var oldColor = span.className;
2087 var oldStyle = span.style.cssText;
2088 if (xPos + s.length < x) {
2089 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
2090 span = document.createElement('span');
2091 line.appendChild(span);
2092 span.className = 'ansi0 bgAnsi15';
2093 span.style.cssText = '';
2094 oldColor = 'ansi0 bgAnsi15';
2101 } while (xPos + s.length < x);
2104 // If styles do not match, create a new <span>
2105 var del = text.length - s.length + x - xPos;
2106 if (oldColor != color ||
2107 (oldStyle != style && (oldStyle || style))) {
2109 // Replacing text at beginning of existing <span>
2110 if (text.length >= s.length) {
2111 // New text is equal or longer than existing text
2114 // Insert new <span> before the current one, then remove leading
2115 // part of existing <span>, adjust style of new <span>, and finally
2117 sibling = document.createElement('span');
2118 line.insertBefore(sibling, span);
2119 this.setTextContent(span, s.substr(text.length));
2124 // Replacing text some way into the existing <span>
2125 var remainder = s.substr(x + text.length - xPos);
2126 this.setTextContent(span, s.substr(0, x - xPos));
2128 sibling = document.createElement('span');
2129 if (span.nextSibling) {
2130 line.insertBefore(sibling, span.nextSibling);
2132 if (remainder.length) {
2133 sibling = document.createElement('span');
2134 sibling.className = oldColor;
2135 sibling.style.cssText = oldStyle;
2136 this.setTextContent(sibling, remainder);
2137 line.insertBefore(sibling, span.nextSibling);
2140 line.appendChild(sibling);
2142 if (remainder.length) {
2143 sibling = document.createElement('span');
2144 sibling.className = oldColor;
2145 sibling.style.cssText = oldStyle;
2146 this.setTextContent(sibling, remainder);
2147 line.appendChild(sibling);
2152 span.className = color;
2153 span.style.cssText = style;
2155 // Overwrite (partial) <span> with new text
2156 s = s.substr(0, x - xPos) +
2158 s.substr(x + text.length - xPos);
2160 this.setTextContent(span, s);
2163 // Delete all subsequent <span>'s that have just been overwritten
2164 sibling = span.nextSibling;
2165 while (del > 0 && sibling) {
2166 s = this.getTextContent(sibling);
2169 line.removeChild(sibling);
2171 sibling = span.nextSibling;
2173 this.setTextContent(sibling, s.substr(del));
2178 // Merge <span> with next sibling, if styles are identical
2179 if (sibling && span.className == sibling.className &&
2180 span.style.cssText == sibling.style.cssText) {
2181 this.setTextContent(span,
2182 this.getTextContent(span) +
2183 this.getTextContent(sibling));
2184 line.removeChild(sibling);
2190 this.cursorX = x + text.length;
2191 if (this.cursorX >= this.terminalWidth) {
2192 this.cursorX = this.terminalWidth - 1;
2193 if (this.cursorX < 0) {
2199 if (!this.cursor.style.visibility) {
2200 var idx = this.cursorX - xPos;
2202 // If we are in a non-empty line, take the cursor Y position from the
2203 // other elements in this line. If dealing with broken, non-proportional
2204 // fonts, this is likely to yield better results.
2205 pixelY = span.offsetTop +
2206 span.offsetParent.offsetTop;
2207 s = this.getTextContent(span);
2208 var nxtIdx = idx - s.length;
2210 this.setTextContent(this.cursor, s.charAt(idx));
2211 pixelX = span.offsetLeft +
2212 idx*span.offsetWidth / s.length;
2215 pixelX = span.offsetLeft + span.offsetWidth;
2217 if (span.nextSibling) {
2218 s = this.getTextContent(span.nextSibling);
2219 this.setTextContent(this.cursor, s.charAt(nxtIdx));
2221 pixelX = span.nextSibling.offsetLeft +
2222 nxtIdx*span.offsetWidth / s.length;
2225 this.setTextContent(this.cursor, ' ');
2229 this.setTextContent(this.cursor, ' ');
2233 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
2236 this.setTextContent(this.space, this.spaces(this.cursorX));
2237 this.cursor.style.left = (this.space.offsetWidth +
2238 console.offsetLeft)/this.scale + 'px';
2240 this.cursorY = yIdx - this.numScrollbackLines;
2242 this.cursor.style.top = pixelY + 'px';
2244 this.cursor.style.top = yIdx*this.cursorHeight +
2245 console.offsetTop + 'px';
2249 // Merge <span> with previous sibling, if styles are identical
2250 if ((sibling = span.previousSibling) &&
2251 span.className == sibling.className &&
2252 span.style.cssText == sibling.style.cssText) {
2253 this.setTextContent(span,
2254 this.getTextContent(sibling) +
2255 this.getTextContent(span));
2256 line.removeChild(sibling);
2259 // Prune white space from the end of the current line
2260 span = line.lastChild;
2262 span.className == 'ansi0 bgAnsi15' &&
2263 !span.style.cssText.length) {
2264 // Scan backwards looking for first non-space character
2265 s = this.getTextContent(span);
2266 for (var i = s.length; i--; ) {
2267 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2268 if (i+1 != s.length) {
2269 this.setTextContent(s.substr(0, i+1));
2277 span = span.previousSibling;
2279 // Remove blank <span>'s from end of line
2280 line.removeChild(sibling);
2282 // Remove entire line (i.e. <div>), if empty
2283 var blank = document.createElement('pre');
2284 blank.style.height = this.cursorHeight + 'px';
2285 this.setTextContent(blank, '\n');
2286 line.parentNode.replaceChild(blank, line);
2293 VT100.prototype.gotoXY = function(x, y) {
2294 if (x >= this.terminalWidth) {
2295 x = this.terminalWidth - 1;
2301 if (this.offsetMode) {
2306 maxY = this.terminalHeight;
2314 this.putString(x, y, '', undefined);
2315 this.needWrap = false;
2318 VT100.prototype.gotoXaY = function(x, y) {
2319 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
2322 VT100.prototype.refreshInvertedState = function() {
2323 if (this.isInverted) {
2324 this.scrollable.className += ' inverted';
2326 this.scrollable.className = this.scrollable.className.
2327 replace(/ *inverted/, '');
2331 VT100.prototype.enableAlternateScreen = function(state) {
2332 // Don't do anything, if we are already on the desired screen
2333 if ((state ? 1 : 0) == this.currentScreen) {
2334 // Calling the resizer is not actually necessary. But it is a good way
2335 // of resetting state that might have gotten corrupted.
2340 // We save the full state of the normal screen, when we switch away from it.
2341 // But for the alternate screen, no saving is necessary. We always reset
2342 // it when we switch to it.
2347 // Display new screen, and initialize state (the resizer does that for us).
2348 this.currentScreen = state ? 1 : 0;
2349 this.console[1-this.currentScreen].style.display = 'none';
2350 this.console[this.currentScreen].style.display = '';
2352 // Select appropriate character pitch.
2353 var transform = this.getTransformName();
2356 // Upon enabling the alternate screen, we switch to 80 column mode. But
2357 // upon returning to the regular screen, we restore the mode that was
2358 // in effect previously.
2359 this.console[1].style[transform] = '';
2362 this.console[this.currentScreen].style[transform];
2363 this.cursor.style[transform] = style;
2364 this.space.style[transform] = style;
2365 this.scale = style == '' ? 1.0:1.65;
2366 if (transform == 'filter') {
2367 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
2372 // If we switched to the alternate screen, reset it completely. Otherwise,
2373 // restore the saved state.
2376 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2378 this.restoreCursor();
2382 VT100.prototype.hideCursor = function() {
2383 var hidden = this.cursor.style.visibility == 'hidden';
2385 this.cursor.style.visibility = 'hidden';
2391 VT100.prototype.showCursor = function(x, y) {
2392 if (this.cursor.style.visibility) {
2393 this.cursor.style.visibility = '';
2394 this.putString(x == undefined ? this.cursorX : x,
2395 y == undefined ? this.cursorY : y,
2402 VT100.prototype.scrollBack = function() {
2403 var i = this.scrollable.scrollTop -
2404 this.scrollable.clientHeight;
2405 this.scrollable.scrollTop = i < 0 ? 0 : i;
2408 VT100.prototype.scrollFore = function() {
2409 var i = this.scrollable.scrollTop +
2410 this.scrollable.clientHeight;
2411 this.scrollable.scrollTop = i > this.numScrollbackLines *
2412 this.cursorHeight + 1
2413 ? this.numScrollbackLines *
2414 this.cursorHeight + 1
2418 VT100.prototype.spaces = function(i) {
2426 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2431 if (w > this.terminalWidth) {
2432 w = this.terminalWidth;
2434 if ((w -= x) <= 0) {
2441 if (h > this.terminalHeight) {
2442 h = this.terminalHeight;
2444 if ((h -= y) <= 0) {
2448 // Special case the situation where we clear the entire screen, and we do
2449 // not have a scrollback buffer. In that case, we should just remove all
2451 if (!this.numScrollbackLines &&
2452 w == this.terminalWidth && h == this.terminalHeight &&
2453 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2454 var console = this.console[this.currentScreen];
2455 while (console.lastChild) {
2456 console.removeChild(console.lastChild);
2458 this.putString(this.cursorX, this.cursorY, '', undefined);
2460 var hidden = this.hideCursor();
2461 var cx = this.cursorX;
2462 var cy = this.cursorY;
2463 var s = this.spaces(w);
2464 for (var i = y+h; i-- > y; ) {
2465 this.putString(x, i, s, color, style);
2467 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2471 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2473 var className = [ ];
2475 var console = this.console[this.currentScreen];
2476 if (sY >= console.childNodes.length) {
2477 text[0] = this.spaces(w);
2478 className[0] = undefined;
2479 style[0] = undefined;
2481 var line = console.childNodes[sY];
2482 if (line.tagName != 'DIV' || !line.childNodes.length) {
2483 text[0] = this.spaces(w);
2484 className[0] = undefined;
2485 style[0] = undefined;
2488 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2489 var s = this.getTextContent(span);
2492 var o = sX > x ? sX - x : 0;
2493 text[text.length] = s.substr(o, w);
2494 className[className.length] = span.className;
2495 style[style.length] = span.style.cssText;
2501 text[text.length] = this.spaces(w);
2502 className[className.length] = undefined;
2503 style[style.length] = undefined;
2507 var hidden = this.hideCursor();
2508 var cx = this.cursorX;
2509 var cy = this.cursorY;
2510 for (var i = 0; i < text.length; i++) {
2513 color = className[i];
2515 color = 'ansi0 bgAnsi15';
2517 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2518 dX += text[i].length;
2520 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2523 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2525 var left = incX < 0 ? -incX : 0;
2526 var right = incX > 0 ? incX : 0;
2527 var up = incY < 0 ? -incY : 0;
2528 var down = incY > 0 ? incY : 0;
2530 // Clip region against terminal size
2531 var dontScroll = null;
2536 if (w > this.terminalWidth - right) {
2537 w = this.terminalWidth - right;
2539 if ((w -= x) <= 0) {
2546 if (h > this.terminalHeight - down) {
2547 h = this.terminalHeight - down;
2553 if (style && style.indexOf('underline')) {
2554 // Different terminal emulators disagree on the attributes that
2555 // are used for scrolling. The consensus seems to be, never to
2556 // fill with underlined spaces. N.B. this is different from the
2557 // cases when the user blanks a region. User-initiated blanking
2558 // always fills with all of the current attributes.
2559 style = style.replace(/text-decoration:underline;/, '');
2562 // Compute current scroll position
2563 var scrollPos = this.numScrollbackLines -
2564 (this.scrollable.scrollTop-1) / this.cursorHeight;
2566 // Determine original cursor position. Hide cursor temporarily to avoid
2567 // visual artifacts.
2568 var hidden = this.hideCursor();
2569 var cx = this.cursorX;
2570 var cy = this.cursorY;
2571 var console = this.console[this.currentScreen];
2573 if (!incX && !x && w == this.terminalWidth) {
2574 // Scrolling entire lines
2577 if (!this.currentScreen && y == -incY &&
2578 h == this.terminalHeight + incY) {
2579 // Scrolling up with adding to the scrollback buffer. This is only
2580 // possible if there are at least as many lines in the console,
2581 // as the terminal is high
2582 while (console.childNodes.length < this.terminalHeight) {
2583 this.insertBlankLine(this.terminalHeight);
2586 // Add new lines at bottom in order to force scrolling
2587 for (var i = 0; i < y; i++) {
2588 this.insertBlankLine(console.childNodes.length, color, style);
2591 // Adjust the number of lines in the scrollback buffer by
2592 // removing excess entries.
2593 this.updateNumScrollbackLines();
2594 while (this.numScrollbackLines >
2595 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2596 console.removeChild(console.firstChild);
2597 this.numScrollbackLines--;
2600 // Mark lines in the scrollback buffer, so that they do not get
2602 for (var i = this.numScrollbackLines, j = -incY;
2603 i-- > 0 && j-- > 0; ) {
2604 console.childNodes[i].className = 'scrollback';
2607 // Scrolling up without adding to the scrollback buffer.
2610 console.childNodes.length >
2611 this.numScrollbackLines + y + incY; ) {
2612 console.removeChild(console.childNodes[
2613 this.numScrollbackLines + y + incY]);
2616 // If we used to have a scrollback buffer, then we must make sure
2617 // that we add back blank lines at the bottom of the terminal.
2618 // Similarly, if we are scrolling in the middle of the screen,
2619 // we must add blank lines to ensure that the bottom of the screen
2620 // does not move up.
2621 if (this.numScrollbackLines > 0 ||
2622 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2623 for (var i = -incY; i-- > 0; ) {
2624 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2633 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2634 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2636 for (var i = incY; i--; ) {
2637 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2641 // Scrolling partial lines
2643 // Scrolling up or horizontally within a line
2644 for (var i = y + this.numScrollbackLines;
2645 i < y + this.numScrollbackLines + h;
2647 this.copyLineSegment(x + incX, i + incY, x, i, w);
2651 for (var i = y + this.numScrollbackLines + h;
2652 i-- > y + this.numScrollbackLines; ) {
2653 this.copyLineSegment(x + incX, i + incY, x, i, w);
2657 // Clear blank regions
2659 this.clearRegion(x, y, incX, h, color, style);
2660 } else if (incX < 0) {
2661 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2664 this.clearRegion(x, y, w, incY, color, style);
2665 } else if (incY < 0) {
2666 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2670 // Reset scroll position
2671 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2672 this.cursorHeight + 1;
2674 // Move cursor back to its original position
2675 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2679 VT100.prototype.copy = function(selection) {
2680 if (selection == undefined) {
2681 selection = this.selection();
2683 this.internalClipboard = undefined;
2684 if (selection.length) {
2687 this.cliphelper.value = selection;
2688 this.cliphelper.select();
2689 this.cliphelper.createTextRange().execCommand('copy');
2691 this.internalClipboard = selection;
2693 this.cliphelper.value = '';
2697 VT100.prototype.copyLast = function() {
2698 // Opening the context menu can remove the selection. We try to prevent this
2699 // from happening, but that is not possible for all browsers. So, instead,
2700 // we compute the selection before showing the menu.
2701 this.copy(this.lastSelection);
2704 VT100.prototype.pasteFnc = function() {
2705 var clipboard = undefined;
2706 if (this.internalClipboard != undefined) {
2707 clipboard = this.internalClipboard;
2710 this.cliphelper.value = '';
2711 this.cliphelper.createTextRange().execCommand('paste');
2712 clipboard = this.cliphelper.value;
2716 this.cliphelper.value = '';
2717 if (clipboard && this.menu.style.visibility == 'hidden') {
2719 this.keysPressed('' + clipboard);
2726 VT100.prototype.pasteBrowserFnc = function() {
2727 var clipboard = prompt("Paste into this box:","");
2728 if (clipboard != undefined) {
2729 return this.keysPressed('' + clipboard);
2733 VT100.prototype.toggleUTF = function() {
2734 this.utfEnabled = !this.utfEnabled;
2736 // We always persist the last value that the user selected. Not necessarily
2737 // the last value that a random program requested.
2738 this.utfPreferred = this.utfEnabled;
2741 VT100.prototype.toggleBell = function() {
2742 this.visualBell = !this.visualBell;
2745 VT100.prototype.toggleSoftKeyboard = function() {
2746 this.softKeyboard = !this.softKeyboard;
2747 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2750 VT100.prototype.deselectKeys = function(elem) {
2751 if (elem && elem.className == 'selected') {
2752 elem.className = '';
2754 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2755 this.deselectKeys(elem);
2759 VT100.prototype.showSoftKeyboard = function() {
2760 // Make sure no key is currently selected
2761 this.lastSelectedKey = undefined;
2762 this.deselectKeys(this.keyboard);
2763 this.isShift = false;
2764 this.showShiftState(false);
2765 this.isCtrl = false;
2766 this.showCtrlState(false);
2768 this.showAltState(false);
2770 this.keyboard.style.left = '0px';
2771 this.keyboard.style.top = '0px';
2772 this.keyboard.style.width = this.container.offsetWidth + 'px';
2773 this.keyboard.style.height = this.container.offsetHeight + 'px';
2774 this.keyboard.style.visibility = 'hidden';
2775 this.keyboard.style.display = '';
2777 var kbd = this.keyboard.firstChild;
2779 var transform = this.getTransformName();
2781 kbd.style[transform] = '';
2782 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2783 scale = (kbd.offsetWidth/
2784 this.container.offsetWidth)/0.9;
2786 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2787 scale = Math.max((kbd.offsetHeight/
2788 this.container.offsetHeight)/0.9);
2790 var style = this.getTransformStyle(transform,
2791 scale > 1.0 ? scale : undefined);
2792 kbd.style[transform] = style;
2794 if (transform == 'filter') {
2797 kbd.style.left = ((this.container.offsetWidth -
2798 kbd.offsetWidth/scale)/2) + 'px';
2799 kbd.style.top = ((this.container.offsetHeight -
2800 kbd.offsetHeight/scale)/2) + 'px';
2802 this.keyboard.style.visibility = 'visible';
2805 VT100.prototype.hideSoftKeyboard = function() {
2806 this.keyboard.style.display = 'none';
2809 VT100.prototype.toggleCursorBlinking = function() {
2810 this.blinkingCursor = !this.blinkingCursor;
2813 VT100.prototype.about = function() {
2814 alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2815 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2816 "For more information check http://shellinabox.com");
2819 VT100.prototype.hideContextMenu = function() {
2820 this.menu.style.visibility = 'hidden';
2821 this.menu.style.top = '-100px';
2822 this.menu.style.left = '-100px';
2823 this.menu.style.width = '0px';
2824 this.menu.style.height = '0px';
2827 VT100.prototype.extendContextMenu = function(entries, actions) {
2830 VT100.prototype.showContextMenu = function(x, y) {
2831 this.menu.innerHTML =
2832 '<table class="popup" ' +
2833 'cellpadding="0" cellspacing="0">' +
2835 '<ul id="menuentries">' +
2836 '<li id="beginclipboard">Copy</li>' +
2837 '<li id="endclipboard">Paste</li>' +
2838 '<li id="browserclipboard">Paste from browser</li>' +
2840 '<li id="reset">Reset</li>' +
2842 '<li id="beginconfig">' +
2843 (this.utfEnabled ? '<img src="/webshell/enabled.gif" />' : '') +
2846 (this.visualBell ? '<img src="/webshell/enabled.gif" />' : '') +
2849 (this.softKeyboard ? '<img src="/webshell/enabled.gif" />' : '') +
2850 'Onscreen Keyboard</li>' +
2851 '<li id="endconfig">' +
2852 (this.blinkingCursor ? '<img src="/webshell/enabled.gif" />' : '') +
2853 'Blinking Cursor</li>'+
2854 (this.usercss.firstChild ?
2855 '<hr id="beginusercss" />' +
2856 this.usercss.innerHTML +
2857 '<hr id="endusercss" />' :
2859 '<li id="about">About...</li>' +
2864 var popup = this.menu.firstChild;
2865 var menuentries = this.getChildById(popup, 'menuentries');
2867 // Determine menu entries that should be disabled
2868 this.lastSelection = this.selection();
2869 if (!this.lastSelection.length) {
2870 menuentries.firstChild.className
2873 var p = this.pasteFnc();
2875 menuentries.childNodes[1].className
2879 // Actions for default items
2880 var actions = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2881 this.toggleUTF, this.toggleBell,
2882 this.toggleSoftKeyboard,
2883 this.toggleCursorBlinking ];
2885 // Actions for user CSS styles (if any)
2886 for (var i = 0; i < this.usercssActions.length; ++i) {
2887 actions[actions.length] = this.usercssActions[i];
2889 actions[actions.length] = this.about;
2891 // Allow subclasses to dynamically add entries to the context menu
2892 this.extendContextMenu(menuentries, actions);
2894 // Hook up event listeners
2895 for (var node = menuentries.firstChild, i = 0; node;
2896 node = node.nextSibling) {
2897 if (node.tagName == 'LI') {
2898 if (node.className != 'disabled') {
2899 this.addListener(node, 'mouseover',
2900 function(vt100, node) {
2902 node.className = 'hover';
2905 this.addListener(node, 'mouseout',
2906 function(vt100, node) {
2908 node.className = '';
2911 this.addListener(node, 'mousedown',
2912 function(vt100, action) {
2913 return function(event) {
2914 vt100.hideContextMenu();
2916 vt100.storeUserSettings();
2917 return vt100.cancelEvent(event || window.event);
2919 }(this, actions[i]));
2920 this.addListener(node, 'mouseup',
2922 return function(event) {
2923 return vt100.cancelEvent(event || window.event);
2926 this.addListener(node, 'mouseclick',
2928 return function(event) {
2929 return vt100.cancelEvent(event || window.event);
2937 // Position menu next to the mouse pointer
2938 this.menu.style.left = '0px';
2939 this.menu.style.top = '0px';
2940 this.menu.style.width = this.container.offsetWidth + 'px';
2941 this.menu.style.height = this.container.offsetHeight + 'px';
2942 popup.style.left = '0px';
2943 popup.style.top = '0px';
2946 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2947 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2952 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2953 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2958 popup.style.left = x + 'px';
2959 popup.style.top = y + 'px';
2961 // Block all other interactions with the terminal emulator
2962 this.addListener(this.menu, 'click', function(vt100) {
2964 vt100.hideContextMenu();
2969 this.menu.style.visibility = '';
2972 VT100.prototype.keysPressed = function(ch) {
2973 for (var i = 0; i < ch.length; i++) {
2974 var c = ch.charCodeAt(i);
2975 this.vt100(c >= 7 && c <= 15 ||
2976 c == 24 || c == 26 || c == 27 || c >= 32
2977 ? String.fromCharCode(c) : '<' + c + '>');
2981 VT100.prototype.applyModifiers = function(ch, event) {
2983 if (event.ctrlKey) {
2984 if (ch >= 32 && ch <= 127) {
2985 // For historic reasons, some control characters are treated specially
2987 case /* 3 */ 51: ch = 27; break;
2988 case /* 4 */ 52: ch = 28; break;
2989 case /* 5 */ 53: ch = 29; break;
2990 case /* 6 */ 54: ch = 30; break;
2991 case /* 7 */ 55: ch = 31; break;
2992 case /* 8 */ 56: ch = 127; break;
2993 case /* ? */ 63: ch = 127; break;
2994 default: ch &= 31; break;
2998 return String.fromCharCode(ch);
3004 VT100.prototype.handleKey = function(event) {
3005 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
3006 // (event.shiftKey || event.ctrlKey || event.altKey ||
3007 // event.metaKey ? ', ' +
3008 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3009 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3012 if (typeof event.charCode != 'undefined') {
3013 // non-IE keypress events have a translated charCode value. Also, our
3014 // fake events generated when receiving keydown events include this data
3016 ch = event.charCode;
3017 key = event.keyCode;
3019 // When sending a keypress event, IE includes the translated character
3020 // code in the keyCode field.
3025 // Apply modifier keys (ctrl and shift)
3029 ch = this.applyModifiers(ch, event);
3031 // By this point, "ch" is either defined and contains the character code, or
3032 // it is undefined and "key" defines the code of a function key
3033 if (ch != undefined) {
3034 this.scrollable.scrollTop = this.numScrollbackLines *
3035 this.cursorHeight + 1;
3037 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
3038 // Many programs have difficulties dealing with parametrized escape
3039 // sequences for function keys. Thus, if ALT is the only modifier
3040 // key, return Emacs-style keycodes for commonly used keys.
3042 case 33: /* Page Up */ ch = '\u001B<'; break;
3043 case 34: /* Page Down */ ch = '\u001B>'; break;
3044 case 37: /* Left */ ch = '\u001Bb'; break;
3045 case 38: /* Up */ ch = '\u001Bp'; break;
3046 case 39: /* Right */ ch = '\u001Bf'; break;
3047 case 40: /* Down */ ch = '\u001Bn'; break;
3048 case 46: /* Delete */ ch = '\u001Bd'; break;
3051 } else if (event.shiftKey && !event.ctrlKey &&
3052 !event.altKey && !event.metaKey) {
3054 case 33: /* Page Up */ this.scrollBack(); return;
3055 case 34: /* Page Down */ this.scrollFore(); return;
3059 if (ch == undefined) {
3061 case 8: /* Backspace */ ch = '\u007f'; break;
3062 case 9: /* Tab */ ch = '\u0009'; break;
3063 case 10: /* Return */ ch = '\u000A'; break;
3064 case 13: /* Enter */ ch = this.crLfMode ?
3065 '\r\n' : '\r'; break;
3066 case 16: /* Shift */ return;
3067 case 17: /* Ctrl */ return;
3068 case 18: /* Alt */ return;
3069 case 19: /* Break */ return;
3070 case 20: /* Caps Lock */ return;
3071 case 27: /* Escape */ ch = '\u001B'; break;
3072 case 33: /* Page Up */ ch = '\u001B[5~'; break;
3073 case 34: /* Page Down */ ch = '\u001B[6~'; break;
3074 case 35: /* End */ ch = '\u001BOF'; break;
3075 case 36: /* Home */ ch = '\u001BOH'; break;
3076 case 37: /* Left */ ch = this.cursorKeyMode ?
3077 '\u001BOD' : '\u001B[D'; break;
3078 case 38: /* Up */ ch = this.cursorKeyMode ?
3079 '\u001BOA' : '\u001B[A'; break;
3080 case 39: /* Right */ ch = this.cursorKeyMode ?
3081 '\u001BOC' : '\u001B[C'; break;
3082 case 40: /* Down */ ch = this.cursorKeyMode ?
3083 '\u001BOB' : '\u001B[B'; break;
3084 case 45: /* Insert */ ch = '\u001B[2~'; break;
3085 case 46: /* Delete */ ch = '\u001B[3~'; break;
3086 case 91: /* Left Window */ return;
3087 case 92: /* Right Window */ return;
3088 case 93: /* Select */ return;
3089 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
3090 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
3091 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
3092 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
3093 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
3094 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
3095 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
3096 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
3097 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
3098 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
3099 case 106: /* * */ ch = this.applyModifiers(42, event); break;
3100 case 107: /* + */ ch = this.applyModifiers(43, event); break;
3101 case 109: /* - */ ch = this.applyModifiers(45, event); break;
3102 case 110: /* . */ ch = this.applyModifiers(46, event); break;
3103 case 111: /* / */ ch = this.applyModifiers(47, event); break;
3104 case 112: /* F1 */ ch = '\u001BOP'; break;
3105 case 113: /* F2 */ ch = '\u001BOQ'; break;
3106 case 114: /* F3 */ ch = '\u001BOR'; break;
3107 case 115: /* F4 */ ch = '\u001BOS'; break;
3108 case 116: /* F5 */ ch = '\u001B[15~'; break;
3109 case 117: /* F6 */ ch = '\u001B[17~'; break;
3110 case 118: /* F7 */ ch = '\u001B[18~'; break;
3111 case 119: /* F8 */ ch = '\u001B[19~'; break;
3112 case 120: /* F9 */ ch = '\u001B[20~'; break;
3113 case 121: /* F10 */ ch = '\u001B[21~'; break;
3114 case 122: /* F11 */ ch = '\u001B[23~'; break;
3115 case 123: /* F12 */ ch = '\u001B[24~'; break;
3116 case 144: /* Num Lock */ return;
3117 case 145: /* Scroll Lock */ return;
3118 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
3119 case 187: /* = */ ch = this.applyModifiers(61, event); break;
3120 case 188: /* , */ ch = this.applyModifiers(44, event); break;
3121 case 189: /* - */ ch = this.applyModifiers(45, event); break;
3122 case 173: /* - */ ch = this.applyModifiers(45, event); break; // FF15 Patch
3123 case 190: /* . */ ch = this.applyModifiers(46, event); break;
3124 case 191: /* / */ ch = this.applyModifiers(47, event); break;
3125 // Conflicts with dead key " on Swiss keyboards
3126 //case 192: /* ` */ ch = this.applyModifiers(96, event); break;
3127 // Conflicts with dead key " on Swiss keyboards
3128 //case 219: /* [ */ ch = this.applyModifiers(91, event); break;
3129 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
3130 // Conflicts with dead key ^ and ` on Swiss keaboards
3131 // ^ and " on French keyboards
3132 //case 221: /* ] */ ch = this.applyModifiers(93, event); break;
3133 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
3136 this.scrollable.scrollTop = this.numScrollbackLines *
3137 this.cursorHeight + 1;
3141 // "ch" now contains the sequence of keycodes to send. But we might still
3142 // have to apply the effects of modifier keys.
3143 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
3144 var start, digit, part1, part2;
3145 if ((start = ch.substr(0, 2)) == '\u001B[') {
3147 part1.length < ch.length &&
3148 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
3149 part1 = ch.substr(0, part1.length + 1);
3151 part2 = ch.substr(part1.length);
3152 if (part1.length > 2) {
3155 } else if (start == '\u001BO') {
3157 part2 = ch.substr(2);
3159 if (part1 != undefined) {
3161 ((event.shiftKey ? 1 : 0) +
3162 (event.altKey|event.metaKey ? 2 : 0) +
3163 (event.ctrlKey ? 4 : 0)) +
3165 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
3170 if (this.menu.style.visibility == 'hidden') {
3171 // this.vt100('R: c=');
3172 // for (var i = 0; i < ch.length; i++)
3173 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
3174 // this.vt100('\r\n');
3175 this.keysPressed(ch);
3179 VT100.prototype.inspect = function(o, d) {
3180 if (d == undefined) {
3184 if (typeof o == 'object' && ++d < 2) {
3187 rc += this.spaces(d * 2) + i + ' -> ';
3189 rc += this.inspect(o[i], d);
3191 rc += '?' + '?' + '?\r\n';
3196 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
3201 VT100.prototype.checkComposedKeys = function(event) {
3202 // Composed keys (at least on Linux) do not generate normal events.
3203 // Instead, they get entered into the text field. We normally catch
3204 // this on the next keyup event.
3205 var s = this.input.value;
3207 this.input.value = '';
3208 if (this.menu.style.visibility == 'hidden') {
3209 this.keysPressed(s);
3214 VT100.prototype.fixEvent = function(event) {
3215 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
3216 // is used as a second-level selector, clear the modifier bits before
3217 // handling the event.
3218 if (event.ctrlKey && event.altKey) {
3220 fake.charCode = event.charCode;
3221 fake.keyCode = event.keyCode;
3222 fake.ctrlKey = false;
3223 fake.shiftKey = event.shiftKey;
3224 fake.altKey = false;
3225 fake.metaKey = event.metaKey;
3229 // Some browsers fail to translate keys, if both shift and alt/meta is
3230 // pressed at the same time. We try to translate those cases, but that
3231 // only works for US keyboard layouts.
3232 if (event.shiftKey) {
3235 switch (this.lastNormalKeyDownEvent.keyCode) {
3236 case 39: /* ' -> " */ u = 39; s = 34; break;
3237 case 44: /* , -> < */ u = 44; s = 60; break;
3238 case 45: /* - -> _ */ u = 45; s = 95; break;
3239 case 46: /* . -> > */ u = 46; s = 62; break;
3240 case 47: /* / -> ? */ u = 47; s = 63; break;
3242 case 48: /* 0 -> ) */ u = 48; s = 41; break;
3243 case 49: /* 1 -> ! */ u = 49; s = 33; break;
3244 case 50: /* 2 -> @ */ u = 50; s = 64; break;
3245 case 51: /* 3 -> # */ u = 51; s = 35; break;
3246 case 52: /* 4 -> $ */ u = 52; s = 36; break;
3247 case 53: /* 5 -> % */ u = 53; s = 37; break;
3248 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
3249 case 55: /* 7 -> & */ u = 55; s = 38; break;
3250 case 56: /* 8 -> * */ u = 56; s = 42; break;
3251 case 57: /* 9 -> ( */ u = 57; s = 40; break;
3253 case 59: /* ; -> : */ u = 59; s = 58; break;
3254 case 61: /* = -> + */ u = 61; s = 43; break;
3255 case 91: /* [ -> { */ u = 91; s = 123; break;
3256 case 92: /* \ -> | */ u = 92; s = 124; break;
3257 case 93: /* ] -> } */ u = 93; s = 125; break;
3258 case 96: /* ` -> ~ */ u = 96; s = 126; break;
3260 case 109: /* - -> _ */ u = 45; s = 95; break;
3261 case 111: /* / -> ? */ u = 47; s = 63; break;
3263 case 186: /* ; -> : */ u = 59; s = 58; break;
3264 case 187: /* = -> + */ u = 61; s = 43; break;
3265 case 188: /* , -> < */ u = 44; s = 60; break;
3266 case 189: /* - -> _ */ u = 45; s = 95; break;
3267 case 173: /* - -> _ */ u = 45; s = 95; break; // FF15 Patch
3268 case 190: /* . -> > */ u = 46; s = 62; break;
3269 case 191: /* / -> ? */ u = 47; s = 63; break;
3270 case 192: /* ` -> ~ */ u = 96; s = 126; break;
3271 case 219: /* [ -> { */ u = 91; s = 123; break;
3272 case 220: /* \ -> | */ u = 92; s = 124; break;
3273 case 221: /* ] -> } */ u = 93; s = 125; break;
3274 case 222: /* ' -> " */ u = 39; s = 34; break;
3277 if (s && (event.charCode == u || event.charCode == 0)) {
3280 fake.keyCode = event.keyCode;
3281 fake.ctrlKey = event.ctrlKey;
3282 fake.shiftKey = event.shiftKey;
3283 fake.altKey = event.altKey;
3284 fake.metaKey = event.metaKey;
3291 VT100.prototype.keyDown = function(event) {
3292 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
3293 // (event.shiftKey || event.ctrlKey || event.altKey ||
3294 // event.metaKey ? ', ' +
3295 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3296 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3298 this.checkComposedKeys(event);
3299 this.lastKeyPressedEvent = undefined;
3300 this.lastKeyDownEvent = undefined;
3301 this.lastNormalKeyDownEvent = event;
3303 // Swiss keyboard conflicts:
3310 // French keyoard conflicts:
3314 event.keyCode == 32 ||
3315 event.keyCode >= 48 && event.keyCode <= 57 ||
3316 event.keyCode >= 65 && event.keyCode <= 90;
3319 event.keyCode == 59 ||
3320 event.keyCode >= 96 && event.keyCode <= 105 ||
3321 event.keyCode == 107 ||
3322 event.keyCode == 192 ||
3323 event.keyCode >= 219 && event.keyCode <= 221 ||
3324 event.keyCode == 223 ||
3325 event.keyCode == 226;
3328 event.keyCode == 61 ||
3329 event.keyCode == 106 ||
3330 event.keyCode >= 109 && event.keyCode <= 111 ||
3331 event.keyCode >= 186 && event.keyCode <= 191 ||
3332 event.keyCode == 222 ||
3333 event.keyCode == 252;
3335 if (navigator.appName == 'Konqueror') {
3336 normalKey |= event.keyCode < 128;
3341 // We normally prefer to look at keypress events, as they perform the
3342 // translation from keyCode to charCode. This is important, as the
3343 // translation is locale-dependent.
3344 // But for some keys, we must intercept them during the keydown event,
3345 // as they would otherwise get interpreted by the browser.
3346 // Even, when doing all of this, there are some keys that we can never
3347 // intercept. This applies to some of the menu navigation keys in IE.
3348 // In fact, we see them, but we cannot stop IE from seeing them, too.
3349 if ((event.charCode || event.keyCode) &&
3350 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3352 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3353 // interpret this sequence ourselves, as some keyboard layouts use
3354 // it for second-level layouts.
3355 !(event.ctrlKey && event.altKey)) ||
3356 this.catchModifiersEarly && normalKey && !alphNumKey &&
3357 (event.ctrlKey || event.altKey || event.metaKey) ||
3359 this.lastKeyDownEvent = event;
3361 fake.ctrlKey = event.ctrlKey;
3362 fake.shiftKey = event.shiftKey;
3363 fake.altKey = event.altKey;
3364 fake.metaKey = event.metaKey;
3366 fake.charCode = event.keyCode;
3370 fake.keyCode = event.keyCode;
3371 if (!alphNumKey && event.shiftKey) {
3372 fake = this.fixEvent(fake);
3376 this.handleKey(fake);
3377 this.lastNormalKeyDownEvent = undefined;
3380 // For non-IE browsers
3381 event.stopPropagation();
3382 event.preventDefault();
3387 event.cancelBubble = true;
3388 event.returnValue = false;
3398 VT100.prototype.keyPressed = function(event) {
3399 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3400 // (event.shiftKey || event.ctrlKey || event.altKey ||
3401 // event.metaKey ? ', ' +
3402 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3403 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3405 if (this.lastKeyDownEvent) {
3406 // If we already processed the key on keydown, do not process it
3407 // again here. Ideally, the browser should not even have generated a
3408 // keypress event in this case. But that does not appear to always work.
3409 this.lastKeyDownEvent = undefined;
3411 this.handleKey(event.altKey || event.metaKey
3412 ? this.fixEvent(event) : event);
3416 // For non-IE browsers
3417 event.preventDefault();
3423 event.cancelBubble = true;
3424 event.returnValue = false;
3429 this.lastNormalKeyDownEvent = undefined;
3430 this.lastKeyPressedEvent = event;
3434 VT100.prototype.keyUp = function(event) {
3435 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3436 // (event.shiftKey || event.ctrlKey || event.altKey ||
3437 // event.metaKey ? ', ' +
3438 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3439 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3441 if (this.lastKeyPressedEvent) {
3442 // The compose key on Linux occasionally confuses the browser and keeps
3443 // inserting bogus characters into the input field, even if just a regular
3444 // key has been pressed. Detect this case and drop the bogus characters.
3446 event.srcElement).value = '';
3448 // This is usually were we notice that a key has been composed and
3449 // thus failed to generate normal events.
3450 this.checkComposedKeys(event);
3452 // Some browsers don't report keypress events if ctrl or alt is pressed
3453 // for non-alphanumerical keys. Patch things up for now, but in the
3454 // future we will catch these keys earlier (in the keydown handler).
3455 if (this.lastNormalKeyDownEvent) {
3456 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3457 this.catchModifiersEarly = true;
3459 event.keyCode == 32 ||
3460 // Conflicts with dead key ~ (code 50) on French keyboards
3461 //event.keyCode >= 48 && event.keyCode <= 57 ||
3462 event.keyCode >= 48 && event.keyCode <= 49 ||
3463 event.keyCode >= 51 && event.keyCode <= 57 ||
3464 event.keyCode >= 65 && event.keyCode <= 90;
3467 event.keyCode == 50 ||
3468 event.keyCode >= 96 && event.keyCode <= 105;
3471 event.keyCode == 59 || event.keyCode == 61 ||
3472 event.keyCode == 106 || event.keyCode == 107 ||
3473 event.keyCode >= 109 && event.keyCode <= 111 ||
3474 event.keyCode >= 186 && event.keyCode <= 192 ||
3475 event.keyCode >= 219 && event.keyCode <= 223 ||
3476 event.keyCode == 252;
3478 fake.ctrlKey = event.ctrlKey;
3479 fake.shiftKey = event.shiftKey;
3480 fake.altKey = event.altKey;
3481 fake.metaKey = event.metaKey;
3483 fake.charCode = event.keyCode;
3487 fake.keyCode = event.keyCode;
3488 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3489 fake = this.fixEvent(fake);
3492 this.lastNormalKeyDownEvent = undefined;
3493 this.handleKey(fake);
3499 event.cancelBubble = true;
3500 event.returnValue = false;
3505 this.lastKeyDownEvent = undefined;
3506 this.lastKeyPressedEvent = undefined;
3510 VT100.prototype.animateCursor = function(inactive) {
3511 if (!this.cursorInterval) {
3512 this.cursorInterval = setInterval(
3515 vt100.animateCursor();
3517 // Use this opportunity to check whether the user entered a composed
3518 // key, or whether somebody pasted text into the textfield.
3519 vt100.checkComposedKeys();
3523 if (inactive != undefined || this.cursor.className != 'inactive') {
3525 this.cursor.className = 'inactive';
3527 if (this.blinkingCursor) {
3528 this.cursor.className = this.cursor.className == 'bright'
3531 this.cursor.className = 'bright';
3537 VT100.prototype.blurCursor = function() {
3538 this.animateCursor(true);
3541 VT100.prototype.focusCursor = function() {
3542 this.animateCursor(false);
3545 VT100.prototype.flashScreen = function() {
3546 this.isInverted = !this.isInverted;
3547 this.refreshInvertedState();
3548 this.isInverted = !this.isInverted;
3549 setTimeout(function(vt100) {
3551 vt100.refreshInvertedState();
3556 VT100.prototype.beep = function() {
3557 if (this.visualBell) {
3564 this.beeper.src = 'beep.wav';
3571 VT100.prototype.bs = function() {
3572 if (this.cursorX > 0) {
3573 this.gotoXY(this.cursorX - 1, this.cursorY);
3574 this.needWrap = false;
3578 VT100.prototype.ht = function(count) {
3579 if (count == undefined) {
3582 var cx = this.cursorX;
3583 while (count-- > 0) {
3584 while (cx++ < this.terminalWidth) {
3585 var tabState = this.userTabStop[cx];
3586 if (tabState == false) {
3587 // Explicitly cleared tab stop
3589 } else if (tabState) {
3590 // Explicitly set tab stop
3593 // Default tab stop at each eighth column
3600 if (cx > this.terminalWidth - 1) {
3601 cx = this.terminalWidth - 1;
3603 if (cx != this.cursorX) {
3604 this.gotoXY(cx, this.cursorY);
3608 VT100.prototype.rt = function(count) {
3609 if (count == undefined) {
3612 var cx = this.cursorX;
3613 while (count-- > 0) {
3615 var tabState = this.userTabStop[cx];
3616 if (tabState == false) {
3617 // Explicitly cleared tab stop
3619 } else if (tabState) {
3620 // Explicitly set tab stop
3623 // Default tab stop at each eighth column
3633 if (cx != this.cursorX) {
3634 this.gotoXY(cx, this.cursorY);
3638 VT100.prototype.cr = function() {
3639 this.gotoXY(0, this.cursorY);
3640 this.needWrap = false;
3643 VT100.prototype.lf = function(count) {
3644 if (count == undefined) {
3647 if (count > this.terminalHeight) {
3648 count = this.terminalHeight;
3654 while (count-- > 0) {
3655 if (this.cursorY == this.bottom - 1) {
3656 this.scrollRegion(0, this.top + 1,
3657 this.terminalWidth, this.bottom - this.top - 1,
3658 0, -1, this.color, this.style);
3660 } else if (this.cursorY < this.terminalHeight - 1) {
3661 this.gotoXY(this.cursorX, this.cursorY + 1);
3666 VT100.prototype.ri = function(count) {
3667 if (count == undefined) {
3670 if (count > this.terminalHeight) {
3671 count = this.terminalHeight;
3677 while (count-- > 0) {
3678 if (this.cursorY == this.top) {
3679 this.scrollRegion(0, this.top,
3680 this.terminalWidth, this.bottom - this.top - 1,
3681 0, 1, this.color, this.style);
3682 } else if (this.cursorY > 0) {
3683 this.gotoXY(this.cursorX, this.cursorY - 1);
3686 this.needWrap = false;
3689 VT100.prototype.respondID = function() {
3690 this.respondString += '\u001B[?6c';
3693 VT100.prototype.respondSecondaryDA = function() {
3694 this.respondString += '\u001B[>0;0;0c';
3698 VT100.prototype.updateStyle = function() {
3700 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3701 this.style = 'text-decoration: underline;';
3703 var bg = (this.attr >> 4) & 0xF;
3704 var fg = this.attr & 0xF;
3705 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3710 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3711 fg = 8; // Dark grey
3712 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3714 this.style = 'font-weight: bold;';
3716 if (this.attr & 0x1000 /* ATTR_BLINK */) {
3717 this.style = 'text-decoration: blink;';
3719 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3722 VT100.prototype.setAttrColors = function(attr) {
3723 if (attr != this.attr) {
3729 VT100.prototype.saveCursor = function() {
3730 this.savedX[this.currentScreen] = this.cursorX;
3731 this.savedY[this.currentScreen] = this.cursorY;
3732 this.savedAttr[this.currentScreen] = this.attr;
3733 this.savedUseGMap = this.useGMap;
3734 for (var i = 0; i < 4; i++) {
3735 this.savedGMap[i] = this.GMap[i];
3737 this.savedValid[this.currentScreen] = true;
3740 VT100.prototype.restoreCursor = function() {
3741 if (!this.savedValid[this.currentScreen]) {
3744 this.attr = this.savedAttr[this.currentScreen];
3746 this.useGMap = this.savedUseGMap;
3747 for (var i = 0; i < 4; i++) {
3748 this.GMap[i] = this.savedGMap[i];
3750 this.translate = this.GMap[this.useGMap];
3751 this.needWrap = false;
3752 this.gotoXY(this.savedX[this.currentScreen],
3753 this.savedY[this.currentScreen]);
3756 VT100.prototype.getTransformName = function() {
3757 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3758 for (var i = 0; i < styles.length; ++i) {
3759 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3766 VT100.prototype.getTransformStyle = function(transform, scale) {
3767 return scale && scale != 1.0
3768 ? transform == 'filter'
3769 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3770 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3771 "sizingMethod='auto expand')"
3772 : 'translateX(-50%) ' +
3773 'scaleX(' + (1.0/scale) + ') ' +
3778 VT100.prototype.set80_132Mode = function(state) {
3779 var transform = this.getTransformName();
3781 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3785 this.getTransformStyle(transform, 1.65):'';
3786 this.console[this.currentScreen].style[transform] = style;
3787 this.cursor.style[transform] = style;
3788 this.space.style[transform] = style;
3789 this.scale = state ? 1.65 : 1.0;
3790 if (transform == 'filter') {
3791 this.console[this.currentScreen].style.width = state ? '165%' : '';
3797 VT100.prototype.setMode = function(state) {
3798 for (var i = 0; i <= this.npar; i++) {
3799 if (this.isQuestionMark) {
3800 switch (this.par[i]) {
3801 case 1: this.cursorKeyMode = state; break;
3802 case 3: this.set80_132Mode(state); break;
3803 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3804 case 6: this.offsetMode = state; break;
3805 case 7: this.autoWrapMode = state; break;
3807 case 9: this.mouseReporting = state; break;
3808 case 25: this.cursorNeedsShowing = state;
3809 if (state) { this.showCursor(); }
3810 else { this.hideCursor(); } break;
3813 case 47: this.enableAlternateScreen(state); break;
3817 switch (this.par[i]) {
3818 case 3: this.dispCtrl = state; break;
3819 case 4: this.insertMode = state; break;
3820 case 20:this.crLfMode = state; break;
3827 VT100.prototype.statusReport = function() {
3828 // Ready and operational.
3829 this.respondString += '\u001B[0n';
3832 VT100.prototype.cursorReport = function() {
3833 this.respondString += '\u001B[' +
3834 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3836 (this.cursorX + 1) +
3840 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3841 // Changing of cursor color is not implemented.
3844 VT100.prototype.openPrinterWindow = function() {
3847 if (!this.printWin || this.printWin.closed) {
3848 this.printWin = window.open('', 'print-output',
3849 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3850 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3851 this.printWin.document.body.innerHTML =
3852 '<link rel="stylesheet" href="' +
3853 document.location.protocol + '//' + document.location.host +
3854 document.location.pathname.replace(/[^/]*$/, '') +
3855 'print-styles.css" type="text/css">\n' +
3856 '<div id="options"><input id="autoprint" type="checkbox"' +
3857 (this.autoprint ? ' checked' : '') + '>' +
3858 'Automatically, print page(s) when job is ready' +
3859 '</input></div>\n' +
3860 '<div id="spacer"><input type="checkbox"> </input></div>' +
3861 '<pre id="print"></pre>\n';
3862 var autoprint = this.printWin.document.getElementById('autoprint');
3863 this.addListener(autoprint, 'click',
3864 (function(vt100, autoprint) {
3866 vt100.autoprint = autoprint.checked;
3867 vt100.storeUserSettings();
3870 })(this, autoprint));
3871 this.printWin.document.title = 'ShellInABox Printer Output';
3874 // Maybe, a popup blocker prevented us from working. Better catch the
3875 // exception, so that we won't break the entire terminal session. The
3876 // user probably needs to disable the blocker first before retrying the
3880 rc &= this.printWin && !this.printWin.closed &&
3881 (this.printWin.innerWidth ||
3882 this.printWin.document.documentElement.clientWidth ||
3883 this.printWin.document.body.clientWidth) > 1;
3885 if (!rc && this.printing == 100) {
3886 // Different popup blockers work differently. We try to detect a couple
3887 // of common methods. And then we retry again a brief amount later, as
3888 // false positives are otherwise possible. If we are sure that there is
3889 // a popup blocker in effect, we alert the user to it. This is helpful
3890 // as some popup blockers have minimal or no UI, and the user might not
3891 // notice that they are missing the popup. In any case, we only show at
3892 // most one message per print job.
3893 this.printing = true;
3894 setTimeout((function(win) {
3896 if (!win || win.closed ||
3898 win.document.documentElement.clientWidth ||
3899 win.document.body.clientWidth) <= 1) {
3900 alert('Attempted to print, but a popup blocker ' +
3901 'prevented the printer window from opening');
3904 })(this.printWin), 2000);
3909 VT100.prototype.sendToPrinter = function(s) {
3910 this.openPrinterWindow();
3912 var doc = this.printWin.document;
3913 var print = doc.getElementById('print');
3914 if (print.lastChild && print.lastChild.nodeName == '#text') {
3915 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3917 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3920 // There probably was a more aggressive popup blocker that prevented us
3921 // from accessing the printer windows.
3925 VT100.prototype.sendControlToPrinter = function(ch) {
3926 // We get called whenever doControl() is active. But for the printer, we
3927 // only implement a basic line printer that doesn't understand most of
3928 // the escape sequences of the VT100 terminal. In fact, the only escape
3929 // sequence that we really need to recognize is '^[[5i' for turning the
3935 this.openPrinterWindow();
3936 var doc = this.printWin.document;
3937 var print = doc.getElementById('print');
3938 var chars = print.lastChild &&
3939 print.lastChild.nodeName == '#text' ?
3940 print.lastChild.textContent.length : 0;
3941 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3948 this.openPrinterWindow();
3949 var pageBreak = this.printWin.document.createElement('div');
3950 pageBreak.className = 'pagebreak';
3951 pageBreak.innerHTML = '<hr />';
3952 this.printWin.document.getElementById('print').appendChild(pageBreak);
3956 this.openPrinterWindow();
3957 var lineBreak = this.printWin.document.createElement('br');
3958 this.printWin.document.getElementById('print').appendChild(lineBreak);
3962 this.isEsc = 1 /* ESesc */;
3965 switch (this.isEsc) {
3967 this.isEsc = 0 /* ESnormal */;
3970 this.isEsc = 2 /* ESsquare */;
3976 case 2 /* ESsquare */:
3978 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3979 0, 0, 0, 0, 0, 0, 0, 0 ];
3980 this.isEsc = 3 /* ESgetpars */;
3981 this.isQuestionMark = ch == 0x3F /*?*/;
3982 if (this.isQuestionMark) {
3986 case 3 /* ESgetpars */:
3987 if (ch == 0x3B /*;*/) {
3990 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3991 var par = this.par[this.npar];
3992 if (par == undefined) {
3995 this.par[this.npar] = 10*par + (ch & 0xF);
3998 this.isEsc = 4 /* ESgotpars */;
4001 case 4 /* ESgotpars */:
4002 this.isEsc = 0 /* ESnormal */;
4003 if (this.isQuestionMark) {
4008 this.csii(this.par[0]);
4015 this.isEsc = 0 /* ESnormal */;
4021 // There probably was a more aggressive popup blocker that prevented us
4022 // from accessing the printer windows.
4026 VT100.prototype.csiAt = function(number) {
4031 if (number > this.terminalWidth - this.cursorX) {
4032 number = this.terminalWidth - this.cursorX;
4034 this.scrollRegion(this.cursorX, this.cursorY,
4035 this.terminalWidth - this.cursorX - number, 1,
4036 number, 0, this.color, this.style);
4037 this.needWrap = false;
4040 VT100.prototype.csii = function(number) {
4043 case 0: // Print Screen
4046 case 4: // Stop printing
4048 if (this.printing && this.printWin && !this.printWin.closed) {
4049 var print = this.printWin.document.getElementById('print');
4050 while (print.lastChild &&
4051 print.lastChild.tagName == 'DIV' &&
4052 print.lastChild.className == 'pagebreak') {
4053 // Remove trailing blank pages
4054 print.removeChild(print.lastChild);
4056 if (this.autoprint) {
4057 this.printWin.print();
4062 this.printing = false;
4064 case 5: // Start printing
4065 if (!this.printing && this.printWin && !this.printWin.closed) {
4066 this.printWin.document.getElementById('print').innerHTML = '';
4068 this.printing = 100;
4075 VT100.prototype.csiJ = function(number) {
4077 case 0: // Erase from cursor to end of display
4078 this.clearRegion(this.cursorX, this.cursorY,
4079 this.terminalWidth - this.cursorX, 1,
4080 this.color, this.style);
4081 if (this.cursorY < this.terminalHeight-2) {
4082 this.clearRegion(0, this.cursorY+1,
4083 this.terminalWidth, this.terminalHeight-this.cursorY-1,
4084 this.color, this.style);
4087 case 1: // Erase from start to cursor
4088 if (this.cursorY > 0) {
4089 this.clearRegion(0, 0,
4090 this.terminalWidth, this.cursorY,
4091 this.color, this.style);
4093 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4094 this.color, this.style);
4096 case 2: // Erase whole display
4097 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
4098 this.color, this.style);
4106 VT100.prototype.csiK = function(number) {
4108 case 0: // Erase from cursor to end of line
4109 this.clearRegion(this.cursorX, this.cursorY,
4110 this.terminalWidth - this.cursorX, 1,
4111 this.color, this.style);
4113 case 1: // Erase from start of line to cursor
4114 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4115 this.color, this.style);
4117 case 2: // Erase whole line
4118 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
4119 this.color, this.style);
4127 VT100.prototype.csiL = function(number) {
4128 // Open line by inserting blank line(s)
4129 if (this.cursorY >= this.bottom) {
4135 if (number > this.bottom - this.cursorY) {
4136 number = this.bottom - this.cursorY;
4138 this.scrollRegion(0, this.cursorY,
4139 this.terminalWidth, this.bottom - this.cursorY - number,
4140 0, number, this.color, this.style);
4144 VT100.prototype.csiM = function(number) {
4145 // Delete line(s), scrolling up the bottom of the screen.
4146 if (this.cursorY >= this.bottom) {
4152 if (number > this.bottom - this.cursorY) {
4153 number = bottom - cursorY;
4155 this.scrollRegion(0, this.cursorY + number,
4156 this.terminalWidth, this.bottom - this.cursorY - number,
4157 0, -number, this.color, this.style);
4161 VT100.prototype.csim = function() {
4162 for (var i = 0; i <= this.npar; i++) {
4163 switch (this.par[i]) {
4164 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
4165 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
4166 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
4167 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
4168 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
4169 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
4171 this.translate = this.GMap[this.useGMap];
4172 this.dispCtrl = false;
4173 this.toggleMeta = false;
4176 this.translate = this.CodePage437Map;
4177 this.dispCtrl = true;
4178 this.toggleMeta = false;
4181 this.translate = this.CodePage437Map;
4182 this.dispCtrl = true;
4183 this.toggleMeta = true;
4186 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
4187 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
4188 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
4189 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
4190 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
4191 0x0200 /* ATTR_UNDERLINE */; break;
4192 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
4193 case 49: this.attr |= 0xF0; break;
4195 if (this.par[i] >= 30 && this.par[i] <= 37) {
4196 var fg = this.par[i] - 30;
4197 this.attr = (this.attr & ~0x0F) | fg;
4198 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
4199 var bg = this.par[i] - 40;
4200 this.attr = (this.attr & ~0xF0) | (bg << 4);
4208 VT100.prototype.csiP = function(number) {
4209 // Delete character(s) following cursor
4213 if (number > this.terminalWidth - this.cursorX) {
4214 number = this.terminalWidth - this.cursorX;
4216 this.scrollRegion(this.cursorX + number, this.cursorY,
4217 this.terminalWidth - this.cursorX - number, 1,
4218 -number, 0, this.color, this.style);
4222 VT100.prototype.csiX = function(number) {
4223 // Clear characters following cursor
4227 if (number > this.terminalWidth - this.cursorX) {
4228 number = this.terminalWidth - this.cursorX;
4230 this.clearRegion(this.cursorX, this.cursorY, number, 1,
4231 this.color, this.style);
4235 VT100.prototype.settermCommand = function() {
4236 // Setterm commands are not implemented
4239 VT100.prototype.doControl = function(ch) {
4240 if (this.printing) {
4241 this.sendControlToPrinter(ch);
4246 case 0x00: /* ignored */ break;
4247 case 0x08: this.bs(); break;
4248 case 0x09: this.ht(); break;
4252 case 0x84: this.lf(); if (!this.crLfMode) break;
4253 case 0x0D: this.cr(); break;
4254 case 0x85: this.cr(); this.lf(); break;
4255 case 0x0E: this.useGMap = 1;
4256 this.translate = this.GMap[1];
4257 this.dispCtrl = true; break;
4258 case 0x0F: this.useGMap = 0;
4259 this.translate = this.GMap[0];
4260 this.dispCtrl = false; break;
4262 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
4263 case 0x1B: this.isEsc = 1 /* ESesc */; break;
4264 case 0x7F: /* ignored */ break;
4265 case 0x88: this.userTabStop[this.cursorX] = true; break;
4266 case 0x8D: this.ri(); break;
4267 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
4268 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
4269 case 0x9A: this.respondID(); break;
4270 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
4271 case 0x07: if (this.isEsc != 17 /* EStitle */) {
4275 default: switch (this.isEsc) {
4277 this.isEsc = 0 /* ESnormal */;
4279 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
4280 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
4282 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
4284 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
4286 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
4287 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
4288 /*7*/ case 0x37: this.saveCursor(); break;
4289 /*8*/ case 0x38: this.restoreCursor(); break;
4290 /*>*/ case 0x3E: this.applKeyMode = false; break;
4291 /*=*/ case 0x3D: this.applKeyMode = true; break;
4292 /*D*/ case 0x44: this.lf(); break;
4293 /*E*/ case 0x45: this.cr(); this.lf(); break;
4294 /*M*/ case 0x4D: this.ri(); break;
4295 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
4296 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
4297 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
4298 /*Z*/ case 0x5A: this.respondID(); break;
4299 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
4300 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
4301 /*c*/ case 0x63: this.reset(); break;
4302 /*g*/ case 0x67: this.flashScreen(); break;
4306 case 15 /* ESnonstd */:
4310 /*2*/ case 0x32: this.isEsc = 17 /* EStitle */; this.titleString = ''; break;
4311 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4312 this.isEsc = 16 /* ESpalette */; break;
4313 /*R*/ case 0x52: // Palette support is not implemented
4314 this.isEsc = 0 /* ESnormal */; break;
4315 default: this.isEsc = 0 /* ESnormal */; break;
4318 case 16 /* ESpalette */:
4319 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4320 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4321 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4322 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
4324 if (this.npar == 7) {
4325 // Palette support is not implemented
4326 this.isEsc = 0 /* ESnormal */;
4329 this.isEsc = 0 /* ESnormal */;
4332 case 2 /* ESsquare */:
4334 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
4335 0, 0, 0, 0, 0, 0, 0, 0 ];
4336 this.isEsc = 3 /* ESgetpars */;
4337 /*[*/ if (ch == 0x5B) { // Function key
4338 this.isEsc = 6 /* ESfunckey */;
4341 /*?*/ this.isQuestionMark = ch == 0x3F;
4342 if (this.isQuestionMark) {
4347 case 5 /* ESdeviceattr */:
4348 case 3 /* ESgetpars */:
4349 /*;*/ if (ch == 0x3B) {
4352 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4353 var par = this.par[this.npar];
4354 if (par == undefined) {
4357 this.par[this.npar] = 10*par + (ch & 0xF);
4359 } else if (this.isEsc == 5 /* ESdeviceattr */) {
4361 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
4362 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
4363 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
4364 /*p*/ case 0x70: /* set pointer mode resource value */ break;
4367 this.isEsc = 0 /* ESnormal */;
4370 this.isEsc = 4 /* ESgotpars */;
4373 case 4 /* ESgotpars */:
4374 this.isEsc = 0 /* ESnormal */;
4375 if (this.isQuestionMark) {
4377 /*h*/ case 0x68: this.setMode(true); break;
4378 /*l*/ case 0x6C: this.setMode(false); break;
4379 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
4382 this.isQuestionMark = false;
4386 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
4387 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
4389 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
4390 /*A*/ case 0x41: this.gotoXY(this.cursorX,
4391 this.cursorY - (this.par[0] ? this.par[0] : 1));
4394 /*e*/ case 0x65: this.gotoXY(this.cursorX,
4395 this.cursorY + (this.par[0] ? this.par[0] : 1));
4398 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4399 this.cursorY); break;
4400 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4401 this.cursorY); break;
4402 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4404 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4406 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
4408 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
4409 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
4410 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
4411 /*i*/ case 0x69: this.csii(this.par[0]); break;
4412 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
4413 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
4414 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
4415 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
4416 /*m*/ case 0x6D: this.csim(); break;
4417 /*P*/ case 0x50: this.csiP(this.par[0]); break;
4418 /*X*/ case 0x58: this.csiX(this.par[0]); break;
4419 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4420 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4421 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4422 /*g*/ case 0x67: if (this.par[0] == 0) {
4423 this.userTabStop[this.cursorX] = false;
4424 } else if (this.par[0] == 2 || this.par[0] == 3) {
4425 this.userTabStop = [ ];
4426 for (var i = 0; i < this.terminalWidth; i++) {
4427 this.userTabStop[i] = false;
4431 /*h*/ case 0x68: this.setMode(true); break;
4432 /*l*/ case 0x6C: this.setMode(false); break;
4433 /*n*/ case 0x6E: switch (this.par[0]) {
4434 case 5: this.statusReport(); break;
4435 case 6: this.cursorReport(); break;
4439 /*q*/ case 0x71: // LED control not implemented
4441 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4442 var b = this.par[1] ? this.par[1]
4443 : this.terminalHeight;
4444 if (t < b && b <= this.terminalHeight) {
4450 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4451 if (c > this.terminalWidth * this.terminalHeight) {
4452 c = this.terminalWidth * this.terminalHeight;
4455 lineBuf += this.lastCharacter;
4458 /*s*/ case 0x73: this.saveCursor(); break;
4459 /*u*/ case 0x75: this.restoreCursor(); break;
4460 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4461 /*]*/ case 0x5D: this.settermCommand(); break;
4465 case 12 /* ESbang */:
4469 this.isEsc = 0 /* ESnormal */;
4471 case 13 /* ESpercent */:
4472 this.isEsc = 0 /* ESnormal */;
4474 /*@*/ case 0x40: this.utfEnabled = false; break;
4476 /*8*/ case 0x38: this.utfEnabled = true; break;
4480 case 6 /* ESfunckey */:
4481 this.isEsc = 0 /* ESnormal */; break;
4482 case 7 /* EShash */:
4483 this.isEsc = 0 /* ESnormal */;
4484 /*8*/ if (ch == 0x38) {
4485 // Screen alignment test not implemented
4488 case 8 /* ESsetG0 */:
4489 case 9 /* ESsetG1 */:
4490 case 10 /* ESsetG2 */:
4491 case 11 /* ESsetG3 */:
4492 var g = this.isEsc - 8 /* ESsetG0 */;
4493 this.isEsc = 0 /* ESnormal */;
4495 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4497 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4498 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4499 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4502 if (this.useGMap == g) {
4503 this.translate = this.GMap[g];
4506 case 17 /* EStitle */:
4508 if (this.titleString && this.titleString.charAt(0) == ';') {
4509 this.titleString = this.titleString.substr(1);
4510 if (this.titleString != '') {
4511 this.titleString += ' - ';
4513 this.titleString += 'Shell In A Box'
4516 window.document.title = this.titleString;
4519 this.isEsc = 0 /* ESnormal */;
4521 this.titleString += String.fromCharCode(ch);
4524 case 18 /* ESss2 */:
4525 case 19 /* ESss3 */:
4527 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4528 [this.toggleMeta ? (ch | 0x80) : ch];
4529 if ((ch & 0xFF00) == 0xF000) {
4531 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4532 this.isEsc = 0 /* ESnormal */; break;
4535 this.lastCharacter = String.fromCharCode(ch);
4536 lineBuf += this.lastCharacter;
4537 this.isEsc = 0 /* ESnormal */; break;
4539 this.isEsc = 0 /* ESnormal */; break;
4546 VT100.prototype.renderString = function(s, showCursor) {
4547 if (this.printing) {
4548 this.sendToPrinter(s);
4555 // We try to minimize the number of DOM operations by coalescing individual
4556 // characters into strings. This is a significant performance improvement.
4557 var incX = s.length;
4558 if (incX > this.terminalWidth - this.cursorX) {
4559 incX = this.terminalWidth - this.cursorX;
4563 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4566 // Minimize the number of calls to putString(), by avoiding a direct
4567 // call to this.showCursor()
4568 this.cursor.style.visibility = '';
4570 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4573 VT100.prototype.vt100 = function(s) {
4574 this.cursorNeedsShowing = this.hideCursor();
4575 this.respondString = '';
4577 for (var i = 0; i < s.length; i++) {
4578 var ch = s.charCodeAt(i);
4579 if (this.utfEnabled) {
4580 // Decode UTF8 encoded character
4582 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4583 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4584 if (--this.utfCount <= 0) {
4585 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4594 if ((ch & 0xE0) == 0xC0) {
4596 this.utfChar = ch & 0x1F;
4597 } else if ((ch & 0xF0) == 0xE0) {
4599 this.utfChar = ch & 0x0F;
4600 } else if ((ch & 0xF8) == 0xF0) {
4602 this.utfChar = ch & 0x07;
4603 } else if ((ch & 0xFC) == 0xF8) {
4605 this.utfChar = ch & 0x03;
4606 } else if ((ch & 0xFE) == 0xFC) {
4608 this.utfChar = ch & 0x01;
4618 var isNormalCharacter =
4619 (ch >= 32 && ch <= 127 || ch >= 160 ||
4620 this.utfEnabled && ch >= 128 ||
4621 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4622 (ch != 0x7F || this.dispCtrl);
4624 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4626 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4628 if ((ch & 0xFF00) == 0xF000) {
4630 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4633 if (!this.printing) {
4634 if (this.needWrap || this.insertMode) {
4636 this.renderString(lineBuf);
4640 if (this.needWrap) {
4641 this.cr(); this.lf();
4643 if (this.insertMode) {
4644 this.scrollRegion(this.cursorX, this.cursorY,
4645 this.terminalWidth - this.cursorX - 1, 1,
4646 1, 0, this.color, this.style);
4649 this.lastCharacter = String.fromCharCode(ch);
4650 lineBuf += this.lastCharacter;
4651 if (!this.printing &&
4652 this.cursorX + lineBuf.length >= this.terminalWidth) {
4653 this.needWrap = this.autoWrapMode;
4657 this.renderString(lineBuf);
4660 var expand = this.doControl(ch);
4661 if (expand.length) {
4662 var r = this.respondString;
4663 this.respondString= r + this.vt100(expand);
4668 this.renderString(lineBuf, this.cursorNeedsShowing);
4669 } else if (this.cursorNeedsShowing) {
4672 return this.respondString;
4675 VT100.prototype.Latin1Map = [
4676 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4677 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4678 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4679 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4680 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4681 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4682 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4683 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4684 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4685 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4686 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4687 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4688 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4689 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4690 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4691 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4692 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4693 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4694 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4695 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4696 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4697 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4698 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4699 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4700 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4701 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4702 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4703 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4704 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4705 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4706 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4707 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4710 VT100.prototype.VT100GraphicsMap = [
4711 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4712 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4713 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4714 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4715 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4716 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4717 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4718 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4719 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4720 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4721 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4722 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4723 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4724 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4725 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4726 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4727 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4728 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4729 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4730 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4731 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4732 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4733 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4734 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4735 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4736 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4737 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4738 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4739 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4740 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4741 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4742 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4745 VT100.prototype.CodePage437Map = [
4746 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4747 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4748 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4749 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4750 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4751 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4752 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4753 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4754 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4755 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4756 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4757 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4758 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4759 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4760 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4761 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4762 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4763 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4764 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4765 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4766 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4767 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4768 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4769 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4770 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4771 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4772 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4773 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4774 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4775 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4776 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4777 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4780 VT100.prototype.DirectToFontMap = [
4781 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4782 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4783 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4784 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4785 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4786 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4787 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4788 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4789 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4790 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4791 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4792 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4793 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4794 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4795 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4796 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4797 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4798 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4799 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4800 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4801 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4802 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4803 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4804 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4805 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4806 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4807 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4808 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4809 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4810 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4811 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4812 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4815 VT100.prototype.ctrlAction = [
4816 true, false, false, false, false, false, false, true,
4817 true, true, true, true, true, true, true, true,
4818 false, false, false, false, false, false, false, false,
4819 true, false, true, true, false, false, false, false
4822 VT100.prototype.ctrlAlways = [
4823 true, false, false, false, false, false, false, false,
4824 true, false, true, false, true, true, true, true,
4825 false, false, false, false, false, false, false, false,
4826 false, false, false, true, false, false, false, false