1 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
2 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
66 // #define XHR_UNITIALIZED 0
69 // #define XHR_RECEIVING 3
70 // #define XHR_LOADED 4
72 // IE does not define XMLHttpRequest by default, so we provide a suitable
74 if (typeof XMLHttpRequest == 'undefined') {
75 XMLHttpRequest = function() {
76 try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
77 try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
78 try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { }
79 try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
84 function extend(subClass, baseClass) {
85 function inheritance() { }
86 inheritance.prototype = baseClass.prototype;
87 subClass.prototype = new inheritance();
88 subClass.prototype.constructor = subClass;
89 subClass.prototype.superClass = baseClass.prototype;
92 function ShellInABox(url, container) {
93 if (url == undefined) {
94 this.rooturl = document.location.href;
95 this.url = document.location.href.replace(/[?#].*/, '');
100 if (document.location.hash != '') {
101 var hash = decodeURIComponent(document.location.hash).
103 this.nextUrl = hash.replace(/,.*/, '');
104 this.session = hash.replace(/[^,]*,/, '');
106 this.nextUrl = this.url;
109 this.pendingKeys = '';
110 this.keysInFlight = false;
111 this.connected = false;
112 this.superClass.constructor.call(this, container);
114 // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
115 // Chrome never realizes that the page has loaded.
116 setTimeout(function(shellInABox) {
118 shellInABox.sendRequest();
122 extend(ShellInABox, VT100);
124 ShellInABox.prototype.sessionClosed = function() {
126 this.connected = false;
128 this.session = undefined;
129 if (this.cursorX > 0) {
132 this.vt100('Session closed.');
134 this.showReconnect(true);
139 ShellInABox.prototype.reconnect = function() {
140 this.showReconnect(false);
142 if (document.location.hash != '') {
143 // A shellinaboxd daemon launched from a CGI only allows a single
144 // session. In order to reconnect, we must reload the frame definition
145 // and obtain a new port number. As this is a different origin, we
146 // need to get enclosing page to help us.
147 parent.location = this.nextUrl;
149 if (this.url != this.nextUrl) {
150 document.location.replace(this.nextUrl);
152 this.pendingKeys = '';
153 this.keysInFlight = false;
162 ShellInABox.prototype.sendRequest = function(request) {
163 if (request == undefined) {
164 request = new XMLHttpRequest();
166 request.open('POST', this.url + '?', true);
167 request.setRequestHeader('Cache-Control', 'no-cache');
168 request.setRequestHeader('Content-Type',
169 'application/x-www-form-urlencoded; charset=utf-8');
170 var content = 'width=' + this.terminalWidth +
171 '&height=' + this.terminalHeight +
172 (this.session ? '&session=' +
173 encodeURIComponent(this.session) : '&rooturl='+
174 encodeURIComponent(this.rooturl));
175 request.setRequestHeader('Content-Length', content.length);
177 request.onreadystatechange = function(shellInABox) {
180 return shellInABox.onReadyStateChange(request);
182 shellInABox.sessionClosed();
186 request.send(content);
189 ShellInABox.prototype.onReadyStateChange = function(request) {
190 if (request.readyState == 4 /* XHR_LOADED */) {
191 if (request.status == 200) {
192 this.connected = true;
193 var response = eval('(' + request.responseText + ')');
195 this.vt100(response.data);
198 if (!response.session ||
199 this.session && this.session != response.session) {
200 this.sessionClosed();
202 this.session = response.session;
203 this.sendRequest(request);
205 } else if (request.status == 0) {
207 this.sendRequest(request);
209 this.sessionClosed();
214 ShellInABox.prototype.sendKeys = function(keys) {
215 if (!this.connected) {
218 if (this.keysInFlight || this.session == undefined) {
219 this.pendingKeys += keys;
221 this.keysInFlight = true;
222 keys = this.pendingKeys + keys;
223 this.pendingKeys = '';
224 var request = new XMLHttpRequest();
225 request.open('POST', this.url + '?', true);
226 request.setRequestHeader('Cache-Control', 'no-cache');
227 request.setRequestHeader('Content-Type',
228 'application/x-www-form-urlencoded; charset=utf-8');
229 var content = 'width=' + this.terminalWidth +
230 '&height=' + this.terminalHeight +
231 '&session=' +encodeURIComponent(this.session)+
232 '&keys=' + encodeURIComponent(keys);
233 request.setRequestHeader('Content-Length', content.length);
234 request.onreadystatechange = function(shellInABox) {
237 return shellInABox.keyPressReadyStateChange(request);
242 request.send(content);
246 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
247 if (request.readyState == 4 /* XHR_LOADED */) {
248 this.keysInFlight = false;
249 if (this.pendingKeys) {
255 ShellInABox.prototype.keysPressed = function(ch) {
256 var hex = '0123456789ABCDEF';
258 for (var i = 0; i < ch.length; i++) {
259 var c = ch.charCodeAt(i);
261 s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
262 } else if (c < 0x800) {
263 s += hex.charAt(0xC + (c >> 10) ) +
264 hex.charAt( (c >> 6) & 0xF ) +
265 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
266 hex.charAt( c & 0xF );
267 } else if (c < 0x10000) {
269 hex.charAt( (c >> 12) ) +
270 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
271 hex.charAt( (c >> 6) & 0xF ) +
272 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
273 hex.charAt( c & 0xF );
274 } else if (c < 0x110000) {
276 hex.charAt( (c >> 18) ) +
277 hex.charAt(0x8 + ((c >> 16) & 0x3)) +
278 hex.charAt( (c >> 12) & 0xF ) +
279 hex.charAt(0x8 + ((c >> 10) & 0x3)) +
280 hex.charAt( (c >> 6) & 0xF ) +
281 hex.charAt(0x8 + ((c >> 4) & 0x3)) +
282 hex.charAt( c & 0xF );
288 ShellInABox.prototype.resized = function(w, h) {
289 // Do not send a resize request until we are fully initialized.
291 // sendKeys() always transmits the current terminal size. So, flush all
297 ShellInABox.prototype.toggleSSL = function() {
298 if (document.location.hash != '') {
299 if (this.nextUrl.match(/\?plain$/)) {
300 this.nextUrl = this.nextUrl.replace(/\?plain$/, '');
302 this.nextUrl = this.nextUrl.replace(/[?#].*/, '') + '?plain';
305 parent.location = this.nextUrl;
308 this.nextUrl = this.nextUrl.match(/^https:/)
309 ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
310 : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
312 if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
315 if (this.session && this.nextUrl != this.url) {
316 alert('This change will take effect the next time you login.');
320 ShellInABox.prototype.extendContextMenu = function(entries, actions) {
321 // Modify the entries and actions in place, adding any locally defined
323 var oldActions = [ ];
324 for (var i = 0; i < actions.length; i++) {
325 oldActions[i] = actions[i];
327 for (var node = entries.firstChild, i = 0, j = 0; node;
328 node = node.nextSibling) {
329 if (node.tagName == 'LI') {
330 actions[i++] = oldActions[j++];
331 if (node.id == "endconfig") {
333 if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
334 !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
335 // If the server supports both SSL and plain text connections,
336 // provide a menu entry to switch between the two.
337 var newNode = document.createElement('li');
339 if (document.location.hash != '') {
340 isSecure = !this.nextUrl.match(/\?plain$/);
342 isSecure = this.nextUrl.match(/^https:/);
344 newNode.innerHTML = (isSecure ? '✔ ' : '') + 'Secure';
345 if (node.nextSibling) {
346 entries.insertBefore(newNode, node.nextSibling);
348 entries.appendChild(newNode);
350 actions[i++] = this.toggleSSL;
353 node.id = 'endconfig';
360 ShellInABox.prototype.about = function() {
361 alert("Shell In A Box version " + "2.10 (revision 239)" +
362 "\nCopyright 2008-2010 by Markus Gutschke\n" +
363 "For more information check http://shellinabox.com" +
364 (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
366 "This product includes software developed by the OpenSSL Project\n" +
367 "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" +
369 "This product includes cryptographic software written by " +
370 "Eric Young\n(eay@cryptsoft.com)" :
375 // VT100.js -- JavaScript based terminal emulator
376 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
378 // This program is free software; you can redistribute it and/or modify
379 // it under the terms of the GNU General Public License version 2 as
380 // published by the Free Software Foundation.
382 // This program is distributed in the hope that it will be useful,
383 // but WITHOUT ANY WARRANTY; without even the implied warranty of
384 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
385 // GNU General Public License for more details.
387 // You should have received a copy of the GNU General Public License along
388 // with this program; if not, write to the Free Software Foundation, Inc.,
389 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
391 // In addition to these license terms, the author grants the following
392 // additional rights:
394 // If you modify this program, or any covered work, by linking or
395 // combining it with the OpenSSL project's OpenSSL library (or a
396 // modified version of that library), containing parts covered by the
397 // terms of the OpenSSL or SSLeay licenses, the author
398 // grants you additional permission to convey the resulting work.
399 // Corresponding Source for a non-source form of such a combination
400 // shall include the source code for the parts of OpenSSL used as well
401 // as that of the covered work.
403 // You may at your option choose to remove this additional permission from
404 // the work, or from any part of it.
406 // It is possible to build this program in a way that it loads OpenSSL
407 // libraries at run-time. If doing so, the following notices are required
408 // by the OpenSSL and SSLeay licenses:
410 // This product includes software developed by the OpenSSL Project
411 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
413 // This product includes cryptographic software written by Eric Young
414 // (eay@cryptsoft.com)
417 // The most up-to-date version of this program is always available from
418 // http://shellinabox.com
423 // The author believes that for the purposes of this license, you meet the
424 // requirements for publishing the source code, if your web server publishes
425 // the source in unmodified form (i.e. with licensing information, comments,
426 // formatting, and identifier names intact). If there are technical reasons
427 // that require you to make changes to the source code when serving the
428 // JavaScript (e.g to remove pre-processor directives from the source), these
429 // changes should be done in a reversible fashion.
431 // The author does not consider websites that reference this script in
432 // unmodified form, and web servers that serve this script in unmodified form
433 // to be derived works. As such, they are believed to be outside of the
434 // scope of this license and not subject to the rights or restrictions of the
435 // GNU General Public License.
437 // If in doubt, consult a legal professional familiar with the laws that
438 // apply in your country.
440 // #define ESnormal 0
442 // #define ESsquare 2
443 // #define ESgetpars 3
444 // #define ESgotpars 4
445 // #define ESdeviceattr 5
446 // #define ESfunckey 6
450 // #define ESsetG2 10
451 // #define ESsetG3 11
453 // #define ESpercent 13
454 // #define ESignore 14
455 // #define ESnonstd 15
456 // #define ESpalette 16
457 // #define EStitle 17
461 // #define ATTR_DEFAULT 0x00F0
462 // #define ATTR_REVERSE 0x0100
463 // #define ATTR_UNDERLINE 0x0200
464 // #define ATTR_DIM 0x0400
465 // #define ATTR_BRIGHT 0x0800
466 // #define ATTR_BLINK 0x1000
468 // #define MOUSE_DOWN 0
469 // #define MOUSE_UP 1
470 // #define MOUSE_CLICK 2
472 function VT100(container) {
473 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
476 this.urlRE = new RegExp(
477 // Known URL protocol are "http", "https", and "ftp".
478 '(?:http|https|ftp)://' +
480 // Optionally allow username and passwords.
481 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
484 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
485 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
486 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
489 '(?::[1-9][0-9]*)?' +
492 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
494 (linkifyURLs <= 1 ? '' :
495 // Also support URLs without a protocol (assume "http").
496 // Optional username and password.
497 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
499 // Hostnames must end with a well-known top-level domain or must be
501 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
504 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
505 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
506 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
507 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
508 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
509 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
510 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
511 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
512 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
513 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
514 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
515 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
516 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
517 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
520 '(?::[1-9][0-9]{0,4})?' +
523 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
525 // In addition, support e-mail address. Optionally, recognize "mailto:"
526 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
529 '[-_.+a-zA-Z0-9]+@' +
532 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
533 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
534 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
535 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
536 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
537 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
538 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
539 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
540 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
541 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
542 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
543 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
544 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
545 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
547 // Optional arguments
548 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
550 this.getUserSettings();
551 this.initializeElements(container);
552 this.maxScrollbackLines = 500;
555 this.isQuestionMark = false;
558 this.savedAttr = [ ];
559 this.savedUseGMap = 0;
560 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
561 this.CodePage437Map, this.DirectToFontMap ];
562 this.savedValid = [ ];
563 this.respondString = '';
564 this.titleString = '';
565 this.internalClipboard = undefined;
569 VT100.prototype.reset = function(clearHistory) {
570 this.isEsc = 0 /* ESnormal */;
571 this.needWrap = false;
572 this.autoWrapMode = true;
573 this.dispCtrl = false;
574 this.toggleMeta = false;
575 this.insertMode = false;
576 this.applKeyMode = false;
577 this.cursorKeyMode = false;
578 this.crLfMode = false;
579 this.offsetMode = false;
580 this.mouseReporting = false;
581 this.printing = false;
582 if (typeof this.printWin != 'undefined' &&
583 this.printWin && !this.printWin.closed) {
584 this.printWin.close();
586 this.printWin = null;
587 this.utfEnabled = this.utfPreferred;
590 this.color = 'ansi0 bgAnsi15';
592 this.attr = 0x00F0 /* ATTR_DEFAULT */;
594 this.GMap = [ this.Latin1Map,
595 this.VT100GraphicsMap,
597 this.DirectToFontMap];
598 this.translate = this.GMap[this.useGMap];
600 this.bottom = this.terminalHeight;
601 this.lastCharacter = ' ';
602 this.userTabStop = [ ];
605 for (var i = 0; i < 2; i++) {
606 while (this.console[i].firstChild) {
607 this.console[i].removeChild(this.console[i].firstChild);
612 this.enableAlternateScreen(false);
614 var wasCompressed = false;
615 var transform = this.getTransformName();
617 for (var i = 0; i < 2; ++i) {
618 wasCompressed |= this.console[i].style[transform] != '';
619 this.console[i].style[transform] = '';
621 this.cursor.style[transform] = '';
622 this.space.style[transform] = '';
623 if (transform == 'filter') {
624 this.console[this.currentScreen].style.width = '';
634 this.isInverted = false;
635 this.refreshInvertedState();
636 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
637 this.color, this.style);
640 VT100.prototype.addListener = function(elem, event, listener) {
642 if (elem.addEventListener) {
643 elem.addEventListener(event, listener, false);
645 elem.attachEvent('on' + event, listener);
651 VT100.prototype.getUserSettings = function() {
652 // Compute hash signature to identify the entries in the userCSS menu.
653 // If the menu is unchanged from last time, default values can be
654 // looked up in a cookie associated with this page.
656 this.utfPreferred = true;
657 this.visualBell = typeof suppressAllAudio != 'undefined' &&
659 this.autoprint = true;
660 this.softKeyboard = false;
661 this.blinkingCursor = true;
662 if (this.visualBell) {
663 this.signature = Math.floor(16807*this.signature + 1) %
666 if (typeof userCSSList != 'undefined') {
667 for (var i = 0; i < userCSSList.length; ++i) {
668 var label = userCSSList[i][0];
669 for (var j = 0; j < label.length; ++j) {
670 this.signature = Math.floor(16807*this.signature+
671 label.charCodeAt(j)) %
674 if (userCSSList[i][1]) {
675 this.signature = Math.floor(16807*this.signature + 1) %
681 var key = 'shellInABox=' + this.signature + ':';
682 var settings = document.cookie.indexOf(key);
684 settings = document.cookie.substr(settings + key.length).
685 replace(/([0-1]*).*/, "$1");
686 if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
687 0 : userCSSList.length)) {
688 this.utfPreferred = settings.charAt(0) != '0';
689 this.visualBell = settings.charAt(1) != '0';
690 this.autoprint = settings.charAt(2) != '0';
691 this.softKeyboard = settings.charAt(3) != '0';
692 this.blinkingCursor = settings.charAt(4) != '0';
693 if (typeof userCSSList != 'undefined') {
694 for (var i = 0; i < userCSSList.length; ++i) {
695 userCSSList[i][2] = settings.charAt(i + 5) != '0';
700 this.utfEnabled = this.utfPreferred;
703 VT100.prototype.storeUserSettings = function() {
704 var settings = 'shellInABox=' + this.signature + ':' +
705 (this.utfEnabled ? '1' : '0') +
706 (this.visualBell ? '1' : '0') +
707 (this.autoprint ? '1' : '0') +
708 (this.softKeyboard ? '1' : '0') +
709 (this.blinkingCursor ? '1' : '0');
710 if (typeof userCSSList != 'undefined') {
711 for (var i = 0; i < userCSSList.length; ++i) {
712 settings += userCSSList[i][2] ? '1' : '0';
716 d.setDate(d.getDate() + 3653);
717 document.cookie = settings + ';expires=' + d.toGMTString();
720 VT100.prototype.initializeUserCSSStyles = function() {
721 this.usercssActions = [];
722 if (typeof userCSSList != 'undefined') {
725 var wasSingleSel = 1;
726 var beginOfGroup = 0;
727 for (var i = 0; i <= userCSSList.length; ++i) {
728 if (i < userCSSList.length) {
729 var label = userCSSList[i][0];
730 var newGroup = userCSSList[i][1];
731 var enabled = userCSSList[i][2];
733 // Add user style sheet to document
734 var style = document.createElement('link');
735 var id = document.createAttribute('id');
736 id.nodeValue = 'usercss-' + i;
737 style.setAttributeNode(id);
738 var rel = document.createAttribute('rel');
739 rel.nodeValue = 'stylesheet';
740 style.setAttributeNode(rel);
741 var href = document.createAttribute('href');
742 href.nodeValue = 'usercss-' + i + '.css';
743 style.setAttributeNode(href);
744 var type = document.createAttribute('type');
745 type.nodeValue = 'text/css';
746 style.setAttributeNode(type);
747 document.getElementsByTagName('head')[0].appendChild(style);
748 style.disabled = !enabled;
752 if (newGroup || i == userCSSList.length) {
753 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
754 // The last group had multiple entries that are mutually exclusive;
755 // or the previous to last group did. In either case, we need to
756 // append a "<hr />" before we can add the last group to the menu.
759 wasSingleSel = i - beginOfGroup < 1;
763 for (var j = beginOfGroup; j < i; ++j) {
764 this.usercssActions[this.usercssActions.length] =
765 function(vt100, current, begin, count) {
767 // Deselect all other entries in the group, then either select
768 // (for multiple entries in group) or toggle (for on/off entry)
769 // the current entry.
771 var entry = vt100.getChildById(vt100.menu,
775 for (var c = count; c > 0; ++j) {
776 if (entry.tagName == 'LI') {
779 var label = vt100.usercss.childNodes[j];
781 // Restore label to just the text content
782 if (typeof label.textContent == 'undefined') {
783 var s = label.innerText;
784 label.innerHTML = '';
785 label.appendChild(document.createTextNode(s));
787 label.textContent= label.textContent;
790 // User style sheets are numbered sequentially
791 var sheet = document.getElementById(
795 sheet.disabled = !sheet.disabled;
797 sheet.disabled = false;
799 if (!sheet.disabled) {
800 label.innerHTML= '<img src="enabled.gif" />' +
804 sheet.disabled = true;
806 userCSSList[i][2] = !sheet.disabled;
809 entry = entry.nextSibling;
812 // If the font size changed, adjust cursor and line dimensions
813 this.cursor.style.cssText= '';
814 this.cursorWidth = this.cursor.clientWidth;
815 this.cursorHeight = this.lineheight.clientHeight;
816 for (i = 0; i < this.console.length; ++i) {
817 for (var line = this.console[i].firstChild; line;
818 line = line.nextSibling) {
819 line.style.height = this.cursorHeight + 'px';
824 }(this, j, beginOfGroup, i - beginOfGroup);
827 if (i == userCSSList.length) {
833 // Collect all entries in a group, before attaching them to the menu.
834 // This is necessary as we don't know whether this is a group of
835 // mutually exclusive options (which should be separated by "<hr />" on
836 // both ends), or whether this is a on/off toggle, which can be grouped
837 // together with other on/off options.
839 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
843 this.usercss.innerHTML = menu;
847 VT100.prototype.resetLastSelectedKey = function(e) {
848 var key = this.lastSelectedKey;
853 var position = this.mousePosition(e);
855 // We don't get all the necessary events to reliably reselect a key
856 // if we moved away from it and then back onto it. We approximate the
857 // behavior by remembering the key until either we release the mouse
858 // button (we might never get this event if the mouse has since left
859 // the window), or until we move away too far.
860 var box = this.keyboard.firstChild;
861 if (position[0] < box.offsetLeft + key.offsetWidth ||
862 position[1] < box.offsetTop + key.offsetHeight ||
863 position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
864 position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
865 position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
866 position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
867 position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
868 position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
869 if (this.lastSelectedKey.className) log.console('reset: deselecting');
870 this.lastSelectedKey.className = '';
871 this.lastSelectedKey = undefined;
876 VT100.prototype.showShiftState = function(state) {
877 var style = document.getElementById('shift_state');
879 this.setTextContentRaw(style,
880 '#vt100 #keyboard .shifted {' +
881 'display: inline }' +
882 '#vt100 #keyboard .unshifted {' +
885 this.setTextContentRaw(style, '');
887 var elems = this.keyboard.getElementsByTagName('I');
888 for (var i = 0; i < elems.length; ++i) {
889 if (elems[i].id == '16') {
890 elems[i].className = state ? 'selected' : '';
895 VT100.prototype.showCtrlState = function(state) {
896 var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
898 ctrl.className = state ? 'selected' : '';
902 VT100.prototype.showAltState = function(state) {
903 var alt = this.getChildById(this.keyboard, '18' /* Alt */);
905 alt.className = state ? 'selected' : '';
909 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
914 fake.shiftKey = shift;
917 return this.handleKey(fake);
920 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
921 if (elem == undefined) {
924 if (ch == '\u00A0') {
925 // should be treated as a regular space character.
928 if (ch != undefined && CH == undefined) {
929 // For letter keys, we automatically compute the uppercase character code
930 // from the lowercase one.
931 CH = ch.toUpperCase();
933 if (KEY == undefined && key != undefined) {
934 // Most keys have identically key codes for both lowercase and uppercase
935 // keypresses. Normally, only function keys would have distinct key codes,
936 // whereas regular keys have character codes.
938 } else if (KEY == undefined && CH != undefined) {
939 // For regular keys, copy the character code to the key code.
940 KEY = CH.charCodeAt(0);
942 if (key == undefined && ch != undefined) {
943 // For regular keys, copy the character code to the key code.
944 key = ch.charCodeAt(0);
946 // Convert characters to numeric character codes. If the character code
947 // is undefined (i.e. this is a function key), set it to zero.
948 ch = ch ? ch.charCodeAt(0) : 0;
949 CH = CH ? CH.charCodeAt(0) : 0;
951 // Mouse down events high light the key. We also set lastSelectedKey. This
952 // is needed to that mouseout/mouseover can keep track of the key that
953 // is currently being clicked.
954 this.addListener(elem, 'mousedown',
955 function(vt100, elem, key) { return function(e) {
956 if ((e.which || e.button) == 1) {
957 if (vt100.lastSelectedKey) {
958 vt100.lastSelectedKey.className= '';
960 // Highlight the key while the mouse button is held down.
961 if (key == 16 /* Shift */) {
962 if (!elem.className != vt100.isShift) {
963 vt100.showShiftState(!vt100.isShift);
965 } else if (key == 17 /* Ctrl */) {
966 if (!elem.className != vt100.isCtrl) {
967 vt100.showCtrlState(!vt100.isCtrl);
969 } else if (key == 18 /* Alt */) {
970 if (!elem.className != vt100.isAlt) {
971 vt100.showAltState(!vt100.isAlt);
974 elem.className = 'selected';
976 vt100.lastSelectedKey = elem;
978 return false; }; }(this, elem, key));
980 // Modifier keys update the state of the keyboard, but do not generate
981 // any key clicks that get forwarded to the application.
982 key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
983 function(vt100, elem) { return function(e) {
984 if (elem == vt100.lastSelectedKey) {
985 if (key == 16 /* Shift */) {
986 // The user clicked the Shift key
987 vt100.isShift = !vt100.isShift;
988 vt100.showShiftState(vt100.isShift);
989 } else if (key == 17 /* Ctrl */) {
990 vt100.isCtrl = !vt100.isCtrl;
991 vt100.showCtrlState(vt100.isCtrl);
992 } else if (key == 18 /* Alt */) {
993 vt100.isAlt = !vt100.isAlt;
994 vt100.showAltState(vt100.isAlt);
996 vt100.lastSelectedKey = undefined;
998 if (vt100.lastSelectedKey) {
999 vt100.lastSelectedKey.className = '';
1000 vt100.lastSelectedKey = undefined;
1002 return false; }; }(this, elem) :
1003 // Regular keys generate key clicks, when the mouse button is released or
1004 // when a mouse click event is received.
1005 function(vt100, elem, ch, key, CH, KEY) { return function(e) {
1006 if (vt100.lastSelectedKey) {
1007 if (elem == vt100.lastSelectedKey) {
1008 // The user clicked a key.
1009 if (vt100.isShift) {
1010 vt100.clickedKeyboard(e, elem, CH, KEY,
1011 true, vt100.isCtrl, vt100.isAlt);
1013 vt100.clickedKeyboard(e, elem, ch, key,
1014 false, vt100.isCtrl, vt100.isAlt);
1016 vt100.isShift = false;
1017 vt100.showShiftState(false);
1018 vt100.isCtrl = false;
1019 vt100.showCtrlState(false);
1020 vt100.isAlt = false;
1021 vt100.showAltState(false);
1023 vt100.lastSelectedKey.className = '';
1024 vt100.lastSelectedKey = undefined;
1026 elem.className = '';
1027 return false; }; }(this, elem, ch, key, CH, KEY);
1028 this.addListener(elem, 'mouseup', clicked);
1029 this.addListener(elem, 'click', clicked);
1031 // When moving the mouse away from a key, check if any keys need to be
1033 this.addListener(elem, 'mouseout',
1034 function(vt100, elem, key) { return function(e) {
1035 if (key == 16 /* Shift */) {
1036 if (!elem.className == vt100.isShift) {
1037 vt100.showShiftState(vt100.isShift);
1039 } else if (key == 17 /* Ctrl */) {
1040 if (!elem.className == vt100.isCtrl) {
1041 vt100.showCtrlState(vt100.isCtrl);
1043 } else if (key == 18 /* Alt */) {
1044 if (!elem.className == vt100.isAlt) {
1045 vt100.showAltState(vt100.isAlt);
1047 } else if (elem.className) {
1048 elem.className = '';
1049 vt100.lastSelectedKey = elem;
1050 } else if (vt100.lastSelectedKey) {
1051 vt100.resetLastSelectedKey(e);
1053 return false; }; }(this, elem, key));
1055 // When moving the mouse over a key, select it if the user is still holding
1056 // the mouse button down (i.e. elem == lastSelectedKey)
1057 this.addListener(elem, 'mouseover',
1058 function(vt100, elem, key) { return function(e) {
1059 if (elem == vt100.lastSelectedKey) {
1060 if (key == 16 /* Shift */) {
1061 if (!elem.className != vt100.isShift) {
1062 vt100.showShiftState(!vt100.isShift);
1064 } else if (key == 17 /* Ctrl */) {
1065 if (!elem.className != vt100.isCtrl) {
1066 vt100.showCtrlState(!vt100.isCtrl);
1068 } else if (key == 18 /* Alt */) {
1069 if (!elem.className != vt100.isAlt) {
1070 vt100.showAltState(!vt100.isAlt);
1072 } else if (!elem.className) {
1073 elem.className = 'selected';
1076 vt100.resetLastSelectedKey(e);
1078 return false; }; }(this, elem, key));
1081 VT100.prototype.initializeKeyBindings = function(elem) {
1083 if (elem.nodeName == "I" || elem.nodeName == "B") {
1085 // Function keys. The Javascript keycode is part of the "id"
1086 var i = parseInt(elem.id);
1088 // If the id does not parse as a number, it is not a keycode.
1089 this.addKeyBinding(elem, undefined, i);
1092 var child = elem.firstChild;
1094 if (child.nodeName == "#text") {
1095 // If the key only has a text node as a child, then it is a letter.
1096 // Automatically compute the lower and upper case version of the
1098 var text = this.getTextContent(child) ||
1099 this.getTextContent(elem);
1100 this.addKeyBinding(elem, text.toLowerCase());
1101 } else if (child.nextSibling) {
1102 // If the key has two children, they are the lower and upper case
1103 // character code, respectively.
1104 this.addKeyBinding(elem, this.getTextContent(child), undefined,
1105 this.getTextContent(child.nextSibling));
1111 // Recursively parse all other child nodes.
1112 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
1113 this.initializeKeyBindings(elem);
1117 VT100.prototype.initializeKeyboardButton = function() {
1118 // Configure mouse event handlers for button that displays/hides keyboard
1119 this.addListener(this.keyboardImage, 'click',
1120 function(vt100) { return function(e) {
1121 if (vt100.keyboard.style.display != '') {
1122 if (vt100.reconnectBtn.style.visibility != '') {
1123 vt100.initializeKeyboard();
1124 vt100.showSoftKeyboard();
1127 vt100.hideSoftKeyboard();
1128 vt100.input.focus();
1130 return false; }; }(this));
1132 // Enable button that displays keyboard
1133 if (this.softKeyboard) {
1134 this.keyboardImage.style.visibility = 'visible';
1138 VT100.prototype.initializeKeyboard = function() {
1139 // Only need to initialize the keyboard the very first time. When doing so,
1140 // copy the keyboard layout from the iframe.
1141 if (this.keyboard.firstChild) {
1144 this.keyboard.innerHTML =
1145 this.layout.contentDocument.body.innerHTML;
1146 var box = this.keyboard.firstChild;
1147 this.hideSoftKeyboard();
1149 // Configure mouse event handlers for on-screen keyboard
1150 this.addListener(this.keyboard, 'click',
1151 function(vt100) { return function(e) {
1152 vt100.hideSoftKeyboard();
1153 vt100.input.focus();
1154 return false; }; }(this));
1155 this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
1156 this.addListener(box, 'click', this.cancelEvent);
1157 this.addListener(box, 'mouseup',
1158 function(vt100) { return function(e) {
1159 if (vt100.lastSelectedKey) {
1160 vt100.lastSelectedKey.className = '';
1161 vt100.lastSelectedKey = undefined;
1163 return false; }; }(this));
1164 this.addListener(box, 'mouseout',
1165 function(vt100) { return function(e) {
1166 return vt100.resetLastSelectedKey(e); }; }(this));
1167 this.addListener(box, 'mouseover',
1168 function(vt100) { return function(e) {
1169 return vt100.resetLastSelectedKey(e); }; }(this));
1171 // Configure SHIFT key behavior
1172 var style = document.createElement('style');
1173 var id = document.createAttribute('id');
1174 id.nodeValue = 'shift_state';
1175 style.setAttributeNode(id);
1176 var type = document.createAttribute('type');
1177 type.nodeValue = 'text/css';
1178 style.setAttributeNode(type);
1179 document.getElementsByTagName('head')[0].appendChild(style);
1181 // Set up key bindings
1182 this.initializeKeyBindings(box);
1185 VT100.prototype.initializeElements = function(container) {
1186 // If the necessary objects have not already been defined in the HTML
1187 // page, create them now.
1189 this.container = container;
1190 } else if (!(this.container = document.getElementById('vt100'))) {
1191 this.container = document.createElement('div');
1192 this.container.id = 'vt100';
1193 document.body.appendChild(this.container);
1196 if (!this.getChildById(this.container, 'reconnect') ||
1197 !this.getChildById(this.container, 'menu') ||
1198 !this.getChildById(this.container, 'keyboard') ||
1199 !this.getChildById(this.container, 'kbd_button') ||
1200 !this.getChildById(this.container, 'kbd_img') ||
1201 !this.getChildById(this.container, 'layout') ||
1202 !this.getChildById(this.container, 'scrollable') ||
1203 !this.getChildById(this.container, 'console') ||
1204 !this.getChildById(this.container, 'alt_console') ||
1205 !this.getChildById(this.container, 'ieprobe') ||
1206 !this.getChildById(this.container, 'padding') ||
1207 !this.getChildById(this.container, 'cursor') ||
1208 !this.getChildById(this.container, 'lineheight') ||
1209 !this.getChildById(this.container, 'usercss') ||
1210 !this.getChildById(this.container, 'space') ||
1211 !this.getChildById(this.container, 'input') ||
1212 !this.getChildById(this.container, 'cliphelper')) {
1213 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
1214 // we might get a pointless warning that a suitable plugin is not yet
1215 // installed. If in doubt, we'd rather just stay silent.
1218 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
1220 embed = typeof suppressAllAudio != 'undefined' &&
1221 suppressAllAudio ? "" :
1222 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
1223 'id="beep_embed" ' +
1225 'autostart="false" ' +
1227 'enablejavascript="true" ' +
1228 'type="audio/x-wav" ' +
1231 'style="position:absolute;left:-1000px;top:-1000px" />';
1236 this.container.innerHTML =
1237 '<div id="reconnect" style="visibility: hidden">' +
1238 '<input type="button" value="Connect" ' +
1239 'onsubmit="return false" />' +
1241 '<div id="cursize" style="visibility: hidden">' +
1243 '<div id="menu"></div>' +
1244 '<div id="keyboard" unselectable="on">' +
1246 '<div id="scrollable">' +
1247 '<table id="kbd_button">' +
1248 '<tr><td width="100%"> </td>' +
1249 '<td><img id="kbd_img" src="keyboard.png" /></td>' +
1250 '<td> </td></tr>' +
1252 '<pre id="lineheight"> </pre>' +
1253 '<pre id="console">' +
1255 '<div id="ieprobe"><span> </span></div>' +
1257 '<pre id="alt_console" style="display: none"></pre>' +
1258 '<div id="padding"></div>' +
1259 '<pre id="cursor"> </pre>' +
1261 '<div class="hidden">' +
1262 '<div id="usercss"></div>' +
1263 '<pre><div><span id="space"></span></div></pre>' +
1264 '<input type="textfield" id="input" autocorrect="off" autocapitalize="off" />' +
1265 '<input type="textfield" id="cliphelper" />' +
1266 (typeof suppressAllAudio != 'undefined' &&
1267 suppressAllAudio ? "" :
1268 embed + '<bgsound id="beep_bgsound" loop=1 />') +
1269 '<iframe id="layout" src="keyboard.html" />' +
1273 // Find the object used for playing the "beep" sound, if any.
1274 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
1275 this.beeper = undefined;
1277 this.beeper = this.getChildById(this.container,
1279 if (!this.beeper || !this.beeper.Play) {
1280 this.beeper = this.getChildById(this.container,
1282 if (!this.beeper || typeof this.beeper.src == 'undefined') {
1283 this.beeper = undefined;
1288 // Initialize the variables for finding the text console and the
1290 this.reconnectBtn = this.getChildById(this.container,'reconnect');
1291 this.curSizeBox = this.getChildById(this.container, 'cursize');
1292 this.menu = this.getChildById(this.container, 'menu');
1293 this.keyboard = this.getChildById(this.container, 'keyboard');
1294 this.keyboardImage = this.getChildById(this.container, 'kbd_img');
1295 this.layout = this.getChildById(this.container, 'layout');
1296 this.scrollable = this.getChildById(this.container,
1298 this.lineheight = this.getChildById(this.container,
1301 [ this.getChildById(this.container, 'console'),
1302 this.getChildById(this.container, 'alt_console') ];
1303 var ieProbe = this.getChildById(this.container, 'ieprobe');
1304 this.padding = this.getChildById(this.container, 'padding');
1305 this.cursor = this.getChildById(this.container, 'cursor');
1306 this.usercss = this.getChildById(this.container, 'usercss');
1307 this.space = this.getChildById(this.container, 'space');
1308 this.input = this.getChildById(this.container, 'input');
1309 this.cliphelper = this.getChildById(this.container,
1312 // Add any user selectable style sheets to the menu
1313 this.initializeUserCSSStyles();
1315 // Remember the dimensions of a standard character glyph. We would
1316 // expect that we could just check cursor.clientWidth/Height at any time,
1317 // but it turns out that browsers sometimes invalidate these values
1318 // (e.g. while displaying a print preview screen).
1319 this.cursorWidth = this.cursor.clientWidth;
1320 this.cursorHeight = this.lineheight.clientHeight;
1322 // IE has a slightly different boxing model, that we need to compensate for
1323 this.isIE = ieProbe.offsetTop > 1;
1324 ieProbe = undefined;
1325 this.console.innerHTML = '';
1327 // Determine if the terminal window is positioned at the beginning of the
1328 // page, or if it is embedded somewhere else in the page. For full-screen
1329 // terminals, automatically resize whenever the browser window changes.
1330 var marginTop = parseInt(this.getCurrentComputedStyle(
1331 document.body, 'marginTop'));
1332 var marginLeft = parseInt(this.getCurrentComputedStyle(
1333 document.body, 'marginLeft'));
1334 var marginRight = parseInt(this.getCurrentComputedStyle(
1335 document.body, 'marginRight'));
1336 var x = this.container.offsetLeft;
1337 var y = this.container.offsetTop;
1338 for (var parent = this.container; parent = parent.offsetParent; ) {
1339 x += parent.offsetLeft;
1340 y += parent.offsetTop;
1342 this.isEmbedded = marginTop != y ||
1344 (window.innerWidth ||
1345 document.documentElement.clientWidth ||
1346 document.body.clientWidth) -
1347 marginRight != x + this.container.offsetWidth;
1348 if (!this.isEmbedded) {
1349 // Some browsers generate resize events when the terminal is first
1350 // shown. Disable showing the size indicator until a little bit after
1351 // the terminal has been rendered the first time.
1352 this.indicateSize = false;
1353 setTimeout(function(vt100) {
1355 vt100.indicateSize = true;
1358 this.addListener(window, 'resize',
1361 vt100.hideContextMenu();
1363 vt100.showCurrentSize();
1367 // Hide extra scrollbars attached to window
1368 document.body.style.margin = '0px';
1369 try { document.body.style.overflow ='hidden'; } catch (e) { }
1370 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1373 // Set up onscreen soft keyboard
1374 this.initializeKeyboardButton();
1376 // Hide context menu
1377 this.hideContextMenu();
1379 // Add listener to reconnect button
1380 this.addListener(this.reconnectBtn.firstChild, 'click',
1383 var rc = vt100.reconnect();
1384 vt100.input.focus();
1389 // Add input listeners
1390 this.addListener(this.input, 'blur',
1392 return function() { vt100.blurCursor(); } }(this));
1393 this.addListener(this.input, 'focus',
1395 return function() { vt100.focusCursor(); } }(this));
1396 this.addListener(this.input, 'keydown',
1398 return function(e) {
1399 if (!e) e = window.event;
1400 return vt100.keyDown(e); } }(this));
1401 this.addListener(this.input, 'keypress',
1403 return function(e) {
1404 if (!e) e = window.event;
1405 return vt100.keyPressed(e); } }(this));
1406 this.addListener(this.input, 'keyup',
1408 return function(e) {
1409 if (!e) e = window.event;
1410 return vt100.keyUp(e); } }(this));
1412 // Attach listeners that move the focus to the <input> field. This way we
1413 // can make sure that we can receive keyboard input.
1414 var mouseEvent = function(vt100, type) {
1415 return function(e) {
1416 if (!e) e = window.event;
1417 return vt100.mouseEvent(e, type);
1420 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1421 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
1422 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
1424 // Check that browser supports drag and drop
1425 if ('draggable' in document.createElement('span')) {
1426 var dropEvent = function (vt100) {
1427 return function(e) {
1428 if (!e) e = window.event;
1429 if (e.preventDefault) e.preventDefault();
1430 vt100.keysPressed(e.dataTransfer.getData('Text'));
1434 // Tell the browser that we *can* drop on this target
1435 this.addListener(this.scrollable, 'dragover', cancel);
1436 this.addListener(this.scrollable, 'dragenter', cancel);
1438 // Add a listener for the drop event
1439 this.addListener(this.scrollable, 'drop', dropEvent(this));
1442 // Initialize the blank terminal window.
1443 this.currentScreen = 0;
1446 this.numScrollbackLines = 0;
1448 this.bottom = 0x7FFFFFFF;
1455 function cancel(event) {
1456 if (event.preventDefault) {
1457 event.preventDefault();
1462 VT100.prototype.getChildById = function(parent, id) {
1463 var nodeList = parent.all || parent.getElementsByTagName('*');
1464 if (typeof nodeList.namedItem == 'undefined') {
1465 for (var i = 0; i < nodeList.length; i++) {
1466 if (nodeList[i].id == id) {
1472 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1473 return elem ? elem[0] || elem : null;
1477 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1478 if (typeof elem.currentStyle != 'undefined') {
1479 return elem.currentStyle[style];
1481 return document.defaultView.getComputedStyle(elem, null)[style];
1485 VT100.prototype.reconnect = function() {
1489 VT100.prototype.showReconnect = function(state) {
1491 this.hideSoftKeyboard();
1492 this.reconnectBtn.style.visibility = '';
1494 this.reconnectBtn.style.visibility = 'hidden';
1498 VT100.prototype.repairElements = function(console) {
1499 for (var line = console.firstChild; line; line = line.nextSibling) {
1500 if (!line.clientHeight) {
1501 var newLine = document.createElement(line.tagName);
1502 newLine.style.cssText = line.style.cssText;
1503 newLine.className = line.className;
1504 if (line.tagName == 'DIV') {
1505 for (var span = line.firstChild; span; span = span.nextSibling) {
1506 var newSpan = document.createElement(span.tagName);
1507 newSpan.style.cssText = span.style.cssText;
1508 newSpan.className = span.className;
1509 this.setTextContent(newSpan, this.getTextContent(span));
1510 newLine.appendChild(newSpan);
1513 this.setTextContent(newLine, this.getTextContent(line));
1515 line.parentNode.replaceChild(newLine, line);
1521 VT100.prototype.resized = function(w, h) {
1524 VT100.prototype.resizer = function() {
1525 // Hide onscreen soft keyboard
1526 this.hideSoftKeyboard();
1528 // The cursor can get corrupted if the print-preview is displayed in Firefox.
1529 // Recreating it, will repair it.
1530 var newCursor = document.createElement('pre');
1531 this.setTextContent(newCursor, ' ');
1532 newCursor.id = 'cursor';
1533 newCursor.style.cssText = this.cursor.style.cssText;
1534 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1535 if (!newCursor.clientHeight) {
1536 // Things are broken right now. This is probably because we are
1537 // displaying the print-preview. Just don't change any of our settings
1538 // until the print dialog is closed again.
1539 newCursor.parentNode.removeChild(newCursor);
1542 // Swap the old broken cursor for the newly created one.
1543 this.cursor.parentNode.removeChild(this.cursor);
1544 this.cursor = newCursor;
1547 // Really horrible things happen if the contents of the terminal changes
1548 // while the print-preview is showing. We get HTML elements that show up
1549 // in the DOM, but that do not take up any space. Find these elements and
1551 this.repairElements(this.console[0]);
1552 this.repairElements(this.console[1]);
1554 // Lock the cursor size to the size of a normal character. This helps with
1555 // characters that are taller/shorter than normal. Unfortunately, we will
1556 // still get confused if somebody enters a character that is wider/narrower
1557 // than normal. This can happen if the browser tries to substitute a
1558 // characters from a different font.
1559 this.cursor.style.width = this.cursorWidth + 'px';
1560 this.cursor.style.height = this.cursorHeight + 'px';
1562 // Adjust height for one pixel padding of the #vt100 element.
1563 // The latter is necessary to properly display the inactive cursor.
1564 var console = this.console[this.currentScreen];
1565 var height = (this.isEmbedded ? this.container.clientHeight
1566 : (window.innerHeight ||
1567 document.documentElement.clientHeight ||
1568 document.body.clientHeight))-1;
1569 var partial = height % this.cursorHeight;
1570 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1571 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1572 var oldTerminalHeight = this.terminalHeight;
1574 this.updateHeight();
1576 // Clip the cursor to the visible screen.
1577 var cx = this.cursorX;
1578 var cy = this.cursorY + this.numScrollbackLines;
1580 // The alternate screen never keeps a scroll back buffer.
1581 this.updateNumScrollbackLines();
1582 while (this.currentScreen && this.numScrollbackLines > 0) {
1583 console.removeChild(console.firstChild);
1584 this.numScrollbackLines--;
1586 cy -= this.numScrollbackLines;
1589 } else if (cx > this.terminalWidth) {
1590 cx = this.terminalWidth - 1;
1597 } else if (cy > this.terminalHeight) {
1598 cy = this.terminalHeight - 1;
1604 // Clip the scroll region to the visible screen.
1605 if (this.bottom > this.terminalHeight ||
1606 this.bottom == oldTerminalHeight) {
1607 this.bottom = this.terminalHeight;
1609 if (this.top >= this.bottom) {
1610 this.top = this.bottom-1;
1616 // Truncate lines, if necessary. Explicitly reposition cursor (this is
1617 // particularly important after changing the screen number), and reset
1618 // the scroll region to the default.
1619 this.truncateLines(this.terminalWidth);
1620 this.putString(cx, cy, '', undefined);
1621 this.scrollable.scrollTop = this.numScrollbackLines *
1622 this.cursorHeight + 1;
1624 // Update classNames for lines in the scrollback buffer
1625 var line = console.firstChild;
1626 for (var i = 0; i < this.numScrollbackLines; i++) {
1627 line.className = 'scrollback';
1628 line = line.nextSibling;
1631 line.className = '';
1632 line = line.nextSibling;
1635 // Reposition the reconnect button
1636 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1638 this.reconnectBtn.clientWidth)/2 + 'px';
1639 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1640 this.reconnectBtn.clientHeight)/2 + 'px';
1642 // Send notification that the window size has been changed
1643 this.resized(this.terminalWidth, this.terminalHeight);
1646 VT100.prototype.showCurrentSize = function() {
1647 if (!this.indicateSize) {
1650 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1651 this.terminalHeight;
1652 this.curSizeBox.style.left =
1653 (this.terminalWidth*this.cursorWidth/
1655 this.curSizeBox.clientWidth)/2 + 'px';
1656 this.curSizeBox.style.top =
1657 (this.terminalHeight*this.cursorHeight -
1658 this.curSizeBox.clientHeight)/2 + 'px';
1659 this.curSizeBox.style.visibility = '';
1660 if (this.curSizeTimeout) {
1661 clearTimeout(this.curSizeTimeout);
1664 // Only show the terminal size for a short amount of time after resizing.
1665 // Then hide this information, again. Some browsers generate resize events
1666 // throughout the entire resize operation. This is nice, and we will show
1667 // the terminal size while the user is dragging the window borders.
1668 // Other browsers only generate a single event when the user releases the
1669 // mouse. In those cases, we can only show the terminal size once at the
1670 // end of the resize operation.
1671 this.curSizeTimeout = setTimeout(function(vt100) {
1673 vt100.curSizeTimeout = null;
1674 vt100.curSizeBox.style.visibility = 'hidden';
1679 VT100.prototype.selection = function() {
1681 return '' + (window.getSelection && window.getSelection() ||
1682 document.selection && document.selection.type == 'Text' &&
1683 document.selection.createRange().text || '');
1689 VT100.prototype.cancelEvent = function(event) {
1691 // For non-IE browsers
1692 event.stopPropagation();
1693 event.preventDefault();
1698 event.cancelBubble = true;
1699 event.returnValue = false;
1707 VT100.prototype.mousePosition = function(event) {
1708 var offsetX = this.container.offsetLeft;
1709 var offsetY = this.container.offsetTop;
1710 for (var e = this.container; e = e.offsetParent; ) {
1711 offsetX += e.offsetLeft;
1712 offsetY += e.offsetTop;
1714 return [ event.clientX - offsetX,
1715 event.clientY - offsetY ];
1718 VT100.prototype.mouseEvent = function(event, type) {
1719 // If any text is currently selected, do not move the focus as that would
1720 // invalidate the selection.
1721 var selection = this.selection();
1722 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1726 // Compute mouse position in characters.
1727 var position = this.mousePosition(event);
1728 var x = Math.floor(position[0] / this.cursorWidth);
1729 var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1730 this.cursorHeight) - this.numScrollbackLines;
1732 if (x >= this.terminalWidth) {
1733 x = this.terminalWidth - 1;
1740 if (y >= this.terminalHeight) {
1741 y = this.terminalHeight - 1;
1749 // Compute button number and modifier keys.
1750 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
1751 typeof event.pageX != 'undefined' ? event.button :
1752 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1753 if (button != undefined) {
1754 if (event.shiftKey) {
1757 if (event.altKey || event.metaKey) {
1760 if (event.ctrlKey) {
1765 // Report mouse events if they happen inside of the current screen and
1766 // with the SHIFT key unpressed. Both of these restrictions do not apply
1767 // for button releases, as we always want to report those.
1768 if (this.mouseReporting && !selection.length &&
1769 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1770 if (inside || type != 0 /* MOUSE_DOWN */) {
1771 if (button != undefined) {
1772 var report = '\u001B[M' + String.fromCharCode(button + 32) +
1773 String.fromCharCode(x + 33) +
1774 String.fromCharCode(y + 33);
1775 if (type != 2 /* MOUSE_CLICK */) {
1776 this.keysPressed(report);
1779 // If we reported the event, stop propagating it (not sure, if this
1780 // actually works on most browsers; blocking the global "oncontextmenu"
1781 // even is still necessary).
1782 return this.cancelEvent(event);
1787 // Bring up context menu.
1788 if (button == 2 && !event.shiftKey) {
1789 if (type == 0 /* MOUSE_DOWN */) {
1790 this.showContextMenu(position[0], position[1]);
1792 return this.cancelEvent(event);
1795 if (this.mouseReporting) {
1797 event.shiftKey = false;
1805 VT100.prototype.replaceChar = function(s, ch, repl) {
1806 for (var i = -1;;) {
1807 i = s.indexOf(ch, i + 1);
1811 s = s.substr(0, i) + repl + s.substr(i + 1);
1816 VT100.prototype.htmlEscape = function(s) {
1817 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1818 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1821 VT100.prototype.getTextContent = function(elem) {
1822 return elem.textContent ||
1823 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1826 VT100.prototype.setTextContentRaw = function(elem, s) {
1827 // Updating the content of an element is an expensive operation. It actually
1828 // pays off to first check whether the element is still unchanged.
1829 if (typeof elem.textContent == 'undefined') {
1830 if (elem.innerText != s) {
1834 // Very old versions of IE do not allow setting innerText. Instead,
1835 // remove all children, by setting innerHTML and then set the text
1836 // using DOM methods.
1837 elem.innerHTML = '';
1838 elem.appendChild(document.createTextNode(
1839 this.replaceChar(s, ' ', '\u00A0')));
1843 if (elem.textContent != s) {
1844 elem.textContent = s;
1849 VT100.prototype.setTextContent = function(elem, s) {
1850 // Check if we find any URLs in the text. If so, automatically convert them
1852 if (this.urlRE && this.urlRE.test(s)) {
1856 if (RegExp.leftContext != null) {
1857 inner += this.htmlEscape(RegExp.leftContext);
1858 consumed += RegExp.leftContext.length;
1860 var url = this.htmlEscape(RegExp.lastMatch);
1863 // If no protocol was specified, try to guess a reasonable one.
1864 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1865 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1866 var slash = url.indexOf('/');
1867 var at = url.indexOf('@');
1868 var question = url.indexOf('?');
1870 (at < question || question < 0) &&
1871 (slash < 0 || (question > 0 && slash > question))) {
1872 fullUrl = 'mailto:' + url;
1874 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1879 inner += '<a target="vt100Link" href="' + fullUrl +
1880 '">' + url + '</a>';
1881 consumed += RegExp.lastMatch.length;
1882 s = s.substr(consumed);
1883 if (!this.urlRE.test(s)) {
1884 if (RegExp.rightContext != null) {
1885 inner += this.htmlEscape(RegExp.rightContext);
1890 elem.innerHTML = inner;
1894 this.setTextContentRaw(elem, s);
1897 VT100.prototype.insertBlankLine = function(y, color, style) {
1898 // Insert a blank line a position y. This method ignores the scrollback
1899 // buffer. The caller has to add the length of the scrollback buffer to
1900 // the position, if necessary.
1901 // If the position is larger than the number of current lines, this
1902 // method just adds a new line right after the last existing one. It does
1903 // not add any missing lines in between. It is the caller's responsibility
1906 color = 'ansi0 bgAnsi15';
1912 if (color != 'ansi0 bgAnsi15' && !style) {
1913 line = document.createElement('pre');
1914 this.setTextContent(line, '\n');
1916 line = document.createElement('div');
1917 var span = document.createElement('span');
1918 span.style.cssText = style;
1919 span.className = color;
1920 this.setTextContent(span, this.spaces(this.terminalWidth));
1921 line.appendChild(span);
1923 line.style.height = this.cursorHeight + 'px';
1924 var console = this.console[this.currentScreen];
1925 if (console.childNodes.length > y) {
1926 console.insertBefore(line, console.childNodes[y]);
1928 console.appendChild(line);
1932 VT100.prototype.updateWidth = function() {
1933 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1934 this.cursorWidth*this.scale);
1935 return this.terminalWidth;
1938 VT100.prototype.updateHeight = function() {
1939 // We want to be able to display either a terminal window that fills the
1940 // entire browser window, or a terminal window that is contained in a
1941 // <div> which is embededded somewhere in the web page.
1942 if (this.isEmbedded) {
1943 // Embedded terminal. Use size of the containing <div> (id="vt100").
1944 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1947 // Use the full browser window.
1948 this.terminalHeight = Math.floor(((window.innerHeight ||
1949 document.documentElement.clientHeight ||
1950 document.body.clientHeight)-1)/
1953 return this.terminalHeight;
1956 VT100.prototype.updateNumScrollbackLines = function() {
1957 var scrollback = Math.floor(
1958 this.console[this.currentScreen].offsetHeight /
1959 this.cursorHeight) -
1960 this.terminalHeight;
1961 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1962 return this.numScrollbackLines;
1965 VT100.prototype.truncateLines = function(width) {
1969 for (var line = this.console[this.currentScreen].firstChild; line;
1970 line = line.nextSibling) {
1971 if (line.tagName == 'DIV') {
1974 // Traverse current line and truncate it once we saw "width" characters
1975 for (var span = line.firstChild; span;
1976 span = span.nextSibling) {
1977 var s = this.getTextContent(span);
1979 if (x + l > width) {
1980 this.setTextContent(span, s.substr(0, width - x));
1981 while (span.nextSibling) {
1982 line.removeChild(line.lastChild);
1988 // Prune white space from the end of the current line
1989 var span = line.lastChild;
1991 span.className == 'ansi0 bgAnsi15' &&
1992 !span.style.cssText.length) {
1993 // Scan backwards looking for first non-space character
1994 var s = this.getTextContent(span);
1995 for (var i = s.length; i--; ) {
1996 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1997 if (i+1 != s.length) {
1998 this.setTextContent(s.substr(0, i+1));
2006 span = span.previousSibling;
2008 // Remove blank <span>'s from end of line
2009 line.removeChild(sibling);
2011 // Remove entire line (i.e. <div>), if empty
2012 var blank = document.createElement('pre');
2013 blank.style.height = this.cursorHeight + 'px';
2014 this.setTextContent(blank, '\n');
2015 line.parentNode.replaceChild(blank, line);
2023 VT100.prototype.putString = function(x, y, text, color, style) {
2025 color = 'ansi0 bgAnsi15';
2030 var yIdx = y + this.numScrollbackLines;
2036 var console = this.console[this.currentScreen];
2037 if (!text.length && (yIdx >= console.childNodes.length ||
2038 console.childNodes[yIdx].tagName != 'DIV')) {
2039 // Positioning cursor to a blank location
2042 // Create missing blank lines at end of page
2043 while (console.childNodes.length <= yIdx) {
2044 // In order to simplify lookups, we want to make sure that each line
2045 // is represented by exactly one element (and possibly a whole bunch of
2047 // For non-blank lines, we can create a <div> containing one or more
2048 // <span>s. For blank lines, this fails as browsers tend to optimize them
2049 // away. But fortunately, a <pre> tag containing a newline character
2050 // appears to work for all browsers (a would also work, but then
2051 // copying from the browser window would insert superfluous spaces into
2053 this.insertBlankLine(yIdx);
2055 line = console.childNodes[yIdx];
2057 // If necessary, promote blank '\n' line to a <div> tag
2058 if (line.tagName != 'DIV') {
2059 var div = document.createElement('div');
2060 div.style.height = this.cursorHeight + 'px';
2061 div.innerHTML = '<span></span>';
2062 console.replaceChild(div, line);
2066 // Scan through list of <span>'s until we find the one where our text
2068 span = line.firstChild;
2070 while (span.nextSibling && xPos < x) {
2071 len = this.getTextContent(span).length;
2072 if (xPos + len > x) {
2076 span = span.nextSibling;
2080 // If current <span> is not long enough, pad with spaces or add new
2082 s = this.getTextContent(span);
2083 var oldColor = span.className;
2084 var oldStyle = span.style.cssText;
2085 if (xPos + s.length < x) {
2086 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
2087 span = document.createElement('span');
2088 line.appendChild(span);
2089 span.className = 'ansi0 bgAnsi15';
2090 span.style.cssText = '';
2091 oldColor = 'ansi0 bgAnsi15';
2098 } while (xPos + s.length < x);
2101 // If styles do not match, create a new <span>
2102 var del = text.length - s.length + x - xPos;
2103 if (oldColor != color ||
2104 (oldStyle != style && (oldStyle || style))) {
2106 // Replacing text at beginning of existing <span>
2107 if (text.length >= s.length) {
2108 // New text is equal or longer than existing text
2111 // Insert new <span> before the current one, then remove leading
2112 // part of existing <span>, adjust style of new <span>, and finally
2114 sibling = document.createElement('span');
2115 line.insertBefore(sibling, span);
2116 this.setTextContent(span, s.substr(text.length));
2121 // Replacing text some way into the existing <span>
2122 var remainder = s.substr(x + text.length - xPos);
2123 this.setTextContent(span, s.substr(0, x - xPos));
2125 sibling = document.createElement('span');
2126 if (span.nextSibling) {
2127 line.insertBefore(sibling, span.nextSibling);
2129 if (remainder.length) {
2130 sibling = document.createElement('span');
2131 sibling.className = oldColor;
2132 sibling.style.cssText = oldStyle;
2133 this.setTextContent(sibling, remainder);
2134 line.insertBefore(sibling, span.nextSibling);
2137 line.appendChild(sibling);
2139 if (remainder.length) {
2140 sibling = document.createElement('span');
2141 sibling.className = oldColor;
2142 sibling.style.cssText = oldStyle;
2143 this.setTextContent(sibling, remainder);
2144 line.appendChild(sibling);
2149 span.className = color;
2150 span.style.cssText = style;
2152 // Overwrite (partial) <span> with new text
2153 s = s.substr(0, x - xPos) +
2155 s.substr(x + text.length - xPos);
2157 this.setTextContent(span, s);
2160 // Delete all subsequent <span>'s that have just been overwritten
2161 sibling = span.nextSibling;
2162 while (del > 0 && sibling) {
2163 s = this.getTextContent(sibling);
2166 line.removeChild(sibling);
2168 sibling = span.nextSibling;
2170 this.setTextContent(sibling, s.substr(del));
2175 // Merge <span> with next sibling, if styles are identical
2176 if (sibling && span.className == sibling.className &&
2177 span.style.cssText == sibling.style.cssText) {
2178 this.setTextContent(span,
2179 this.getTextContent(span) +
2180 this.getTextContent(sibling));
2181 line.removeChild(sibling);
2187 this.cursorX = x + text.length;
2188 if (this.cursorX >= this.terminalWidth) {
2189 this.cursorX = this.terminalWidth - 1;
2190 if (this.cursorX < 0) {
2196 if (!this.cursor.style.visibility) {
2197 var idx = this.cursorX - xPos;
2199 // If we are in a non-empty line, take the cursor Y position from the
2200 // other elements in this line. If dealing with broken, non-proportional
2201 // fonts, this is likely to yield better results.
2202 pixelY = span.offsetTop +
2203 span.offsetParent.offsetTop;
2204 s = this.getTextContent(span);
2205 var nxtIdx = idx - s.length;
2207 this.setTextContent(this.cursor, s.charAt(idx));
2208 pixelX = span.offsetLeft +
2209 idx*span.offsetWidth / s.length;
2212 pixelX = span.offsetLeft + span.offsetWidth;
2214 if (span.nextSibling) {
2215 s = this.getTextContent(span.nextSibling);
2216 this.setTextContent(this.cursor, s.charAt(nxtIdx));
2218 pixelX = span.nextSibling.offsetLeft +
2219 nxtIdx*span.offsetWidth / s.length;
2222 this.setTextContent(this.cursor, ' ');
2226 this.setTextContent(this.cursor, ' ');
2230 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
2233 this.setTextContent(this.space, this.spaces(this.cursorX));
2234 this.cursor.style.left = (this.space.offsetWidth +
2235 console.offsetLeft)/this.scale + 'px';
2237 this.cursorY = yIdx - this.numScrollbackLines;
2239 this.cursor.style.top = pixelY + 'px';
2241 this.cursor.style.top = yIdx*this.cursorHeight +
2242 console.offsetTop + 'px';
2246 // Merge <span> with previous sibling, if styles are identical
2247 if ((sibling = span.previousSibling) &&
2248 span.className == sibling.className &&
2249 span.style.cssText == sibling.style.cssText) {
2250 this.setTextContent(span,
2251 this.getTextContent(sibling) +
2252 this.getTextContent(span));
2253 line.removeChild(sibling);
2256 // Prune white space from the end of the current line
2257 span = line.lastChild;
2259 span.className == 'ansi0 bgAnsi15' &&
2260 !span.style.cssText.length) {
2261 // Scan backwards looking for first non-space character
2262 s = this.getTextContent(span);
2263 for (var i = s.length; i--; ) {
2264 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2265 if (i+1 != s.length) {
2266 this.setTextContent(s.substr(0, i+1));
2274 span = span.previousSibling;
2276 // Remove blank <span>'s from end of line
2277 line.removeChild(sibling);
2279 // Remove entire line (i.e. <div>), if empty
2280 var blank = document.createElement('pre');
2281 blank.style.height = this.cursorHeight + 'px';
2282 this.setTextContent(blank, '\n');
2283 line.parentNode.replaceChild(blank, line);
2290 VT100.prototype.gotoXY = function(x, y) {
2291 if (x >= this.terminalWidth) {
2292 x = this.terminalWidth - 1;
2298 if (this.offsetMode) {
2303 maxY = this.terminalHeight;
2311 this.putString(x, y, '', undefined);
2312 this.needWrap = false;
2315 VT100.prototype.gotoXaY = function(x, y) {
2316 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
2319 VT100.prototype.refreshInvertedState = function() {
2320 if (this.isInverted) {
2321 this.scrollable.className += ' inverted';
2323 this.scrollable.className = this.scrollable.className.
2324 replace(/ *inverted/, '');
2328 VT100.prototype.enableAlternateScreen = function(state) {
2329 // Don't do anything, if we are already on the desired screen
2330 if ((state ? 1 : 0) == this.currentScreen) {
2331 // Calling the resizer is not actually necessary. But it is a good way
2332 // of resetting state that might have gotten corrupted.
2337 // We save the full state of the normal screen, when we switch away from it.
2338 // But for the alternate screen, no saving is necessary. We always reset
2339 // it when we switch to it.
2344 // Display new screen, and initialize state (the resizer does that for us).
2345 this.currentScreen = state ? 1 : 0;
2346 this.console[1-this.currentScreen].style.display = 'none';
2347 this.console[this.currentScreen].style.display = '';
2349 // Select appropriate character pitch.
2350 var transform = this.getTransformName();
2353 // Upon enabling the alternate screen, we switch to 80 column mode. But
2354 // upon returning to the regular screen, we restore the mode that was
2355 // in effect previously.
2356 this.console[1].style[transform] = '';
2359 this.console[this.currentScreen].style[transform];
2360 this.cursor.style[transform] = style;
2361 this.space.style[transform] = style;
2362 this.scale = style == '' ? 1.0:1.65;
2363 if (transform == 'filter') {
2364 this.console[this.currentScreen].style.width = style == '' ? '165%':'';
2369 // If we switched to the alternate screen, reset it completely. Otherwise,
2370 // restore the saved state.
2373 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2375 this.restoreCursor();
2379 VT100.prototype.hideCursor = function() {
2380 var hidden = this.cursor.style.visibility == 'hidden';
2382 this.cursor.style.visibility = 'hidden';
2388 VT100.prototype.showCursor = function(x, y) {
2389 if (this.cursor.style.visibility) {
2390 this.cursor.style.visibility = '';
2391 this.putString(x == undefined ? this.cursorX : x,
2392 y == undefined ? this.cursorY : y,
2399 VT100.prototype.scrollBack = function() {
2400 var i = this.scrollable.scrollTop -
2401 this.scrollable.clientHeight;
2402 this.scrollable.scrollTop = i < 0 ? 0 : i;
2405 VT100.prototype.scrollFore = function() {
2406 var i = this.scrollable.scrollTop +
2407 this.scrollable.clientHeight;
2408 this.scrollable.scrollTop = i > this.numScrollbackLines *
2409 this.cursorHeight + 1
2410 ? this.numScrollbackLines *
2411 this.cursorHeight + 1
2415 VT100.prototype.spaces = function(i) {
2423 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2428 if (w > this.terminalWidth) {
2429 w = this.terminalWidth;
2431 if ((w -= x) <= 0) {
2438 if (h > this.terminalHeight) {
2439 h = this.terminalHeight;
2441 if ((h -= y) <= 0) {
2445 // Special case the situation where we clear the entire screen, and we do
2446 // not have a scrollback buffer. In that case, we should just remove all
2448 if (!this.numScrollbackLines &&
2449 w == this.terminalWidth && h == this.terminalHeight &&
2450 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2451 var console = this.console[this.currentScreen];
2452 while (console.lastChild) {
2453 console.removeChild(console.lastChild);
2455 this.putString(this.cursorX, this.cursorY, '', undefined);
2457 var hidden = this.hideCursor();
2458 var cx = this.cursorX;
2459 var cy = this.cursorY;
2460 var s = this.spaces(w);
2461 for (var i = y+h; i-- > y; ) {
2462 this.putString(x, i, s, color, style);
2464 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2468 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2470 var className = [ ];
2472 var console = this.console[this.currentScreen];
2473 if (sY >= console.childNodes.length) {
2474 text[0] = this.spaces(w);
2475 className[0] = undefined;
2476 style[0] = undefined;
2478 var line = console.childNodes[sY];
2479 if (line.tagName != 'DIV' || !line.childNodes.length) {
2480 text[0] = this.spaces(w);
2481 className[0] = undefined;
2482 style[0] = undefined;
2485 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2486 var s = this.getTextContent(span);
2489 var o = sX > x ? sX - x : 0;
2490 text[text.length] = s.substr(o, w);
2491 className[className.length] = span.className;
2492 style[style.length] = span.style.cssText;
2498 text[text.length] = this.spaces(w);
2499 className[className.length] = undefined;
2500 style[style.length] = undefined;
2504 var hidden = this.hideCursor();
2505 var cx = this.cursorX;
2506 var cy = this.cursorY;
2507 for (var i = 0; i < text.length; i++) {
2510 color = className[i];
2512 color = 'ansi0 bgAnsi15';
2514 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2515 dX += text[i].length;
2517 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2520 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2522 var left = incX < 0 ? -incX : 0;
2523 var right = incX > 0 ? incX : 0;
2524 var up = incY < 0 ? -incY : 0;
2525 var down = incY > 0 ? incY : 0;
2527 // Clip region against terminal size
2528 var dontScroll = null;
2533 if (w > this.terminalWidth - right) {
2534 w = this.terminalWidth - right;
2536 if ((w -= x) <= 0) {
2543 if (h > this.terminalHeight - down) {
2544 h = this.terminalHeight - down;
2550 if (style && style.indexOf('underline')) {
2551 // Different terminal emulators disagree on the attributes that
2552 // are used for scrolling. The consensus seems to be, never to
2553 // fill with underlined spaces. N.B. this is different from the
2554 // cases when the user blanks a region. User-initiated blanking
2555 // always fills with all of the current attributes.
2556 style = style.replace(/text-decoration:underline;/, '');
2559 // Compute current scroll position
2560 var scrollPos = this.numScrollbackLines -
2561 (this.scrollable.scrollTop-1) / this.cursorHeight;
2563 // Determine original cursor position. Hide cursor temporarily to avoid
2564 // visual artifacts.
2565 var hidden = this.hideCursor();
2566 var cx = this.cursorX;
2567 var cy = this.cursorY;
2568 var console = this.console[this.currentScreen];
2570 if (!incX && !x && w == this.terminalWidth) {
2571 // Scrolling entire lines
2574 if (!this.currentScreen && y == -incY &&
2575 h == this.terminalHeight + incY) {
2576 // Scrolling up with adding to the scrollback buffer. This is only
2577 // possible if there are at least as many lines in the console,
2578 // as the terminal is high
2579 while (console.childNodes.length < this.terminalHeight) {
2580 this.insertBlankLine(this.terminalHeight);
2583 // Add new lines at bottom in order to force scrolling
2584 for (var i = 0; i < y; i++) {
2585 this.insertBlankLine(console.childNodes.length, color, style);
2588 // Adjust the number of lines in the scrollback buffer by
2589 // removing excess entries.
2590 this.updateNumScrollbackLines();
2591 while (this.numScrollbackLines >
2592 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2593 console.removeChild(console.firstChild);
2594 this.numScrollbackLines--;
2597 // Mark lines in the scrollback buffer, so that they do not get
2599 for (var i = this.numScrollbackLines, j = -incY;
2600 i-- > 0 && j-- > 0; ) {
2601 console.childNodes[i].className = 'scrollback';
2604 // Scrolling up without adding to the scrollback buffer.
2607 console.childNodes.length >
2608 this.numScrollbackLines + y + incY; ) {
2609 console.removeChild(console.childNodes[
2610 this.numScrollbackLines + y + incY]);
2613 // If we used to have a scrollback buffer, then we must make sure
2614 // that we add back blank lines at the bottom of the terminal.
2615 // Similarly, if we are scrolling in the middle of the screen,
2616 // we must add blank lines to ensure that the bottom of the screen
2617 // does not move up.
2618 if (this.numScrollbackLines > 0 ||
2619 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2620 for (var i = -incY; i-- > 0; ) {
2621 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2630 console.childNodes.length > this.numScrollbackLines + y + h; ) {
2631 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2633 for (var i = incY; i--; ) {
2634 this.insertBlankLine(this.numScrollbackLines + y, color, style);
2638 // Scrolling partial lines
2640 // Scrolling up or horizontally within a line
2641 for (var i = y + this.numScrollbackLines;
2642 i < y + this.numScrollbackLines + h;
2644 this.copyLineSegment(x + incX, i + incY, x, i, w);
2648 for (var i = y + this.numScrollbackLines + h;
2649 i-- > y + this.numScrollbackLines; ) {
2650 this.copyLineSegment(x + incX, i + incY, x, i, w);
2654 // Clear blank regions
2656 this.clearRegion(x, y, incX, h, color, style);
2657 } else if (incX < 0) {
2658 this.clearRegion(x + w + incX, y, -incX, h, color, style);
2661 this.clearRegion(x, y, w, incY, color, style);
2662 } else if (incY < 0) {
2663 this.clearRegion(x, y + h + incY, w, -incY, color, style);
2667 // Reset scroll position
2668 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2669 this.cursorHeight + 1;
2671 // Move cursor back to its original position
2672 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2676 VT100.prototype.copy = function(selection) {
2677 if (selection == undefined) {
2678 selection = this.selection();
2680 this.internalClipboard = undefined;
2681 if (selection.length) {
2684 this.cliphelper.value = selection;
2685 this.cliphelper.select();
2686 this.cliphelper.createTextRange().execCommand('copy');
2688 this.internalClipboard = selection;
2690 this.cliphelper.value = '';
2694 VT100.prototype.copyLast = function() {
2695 // Opening the context menu can remove the selection. We try to prevent this
2696 // from happening, but that is not possible for all browsers. So, instead,
2697 // we compute the selection before showing the menu.
2698 this.copy(this.lastSelection);
2701 VT100.prototype.pasteFnc = function() {
2702 var clipboard = undefined;
2703 if (this.internalClipboard != undefined) {
2704 clipboard = this.internalClipboard;
2707 this.cliphelper.value = '';
2708 this.cliphelper.createTextRange().execCommand('paste');
2709 clipboard = this.cliphelper.value;
2713 this.cliphelper.value = '';
2714 if (clipboard && this.menu.style.visibility == 'hidden') {
2716 this.keysPressed('' + clipboard);
2723 VT100.prototype.pasteBrowserFnc = function() {
2724 var clipboard = prompt("Paste into this box:","");
2725 if (clipboard != undefined) {
2726 return this.keysPressed('' + clipboard);
2730 VT100.prototype.toggleUTF = function() {
2731 this.utfEnabled = !this.utfEnabled;
2733 // We always persist the last value that the user selected. Not necessarily
2734 // the last value that a random program requested.
2735 this.utfPreferred = this.utfEnabled;
2738 VT100.prototype.toggleBell = function() {
2739 this.visualBell = !this.visualBell;
2742 VT100.prototype.toggleSoftKeyboard = function() {
2743 this.softKeyboard = !this.softKeyboard;
2744 this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2747 VT100.prototype.deselectKeys = function(elem) {
2748 if (elem && elem.className == 'selected') {
2749 elem.className = '';
2751 for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2752 this.deselectKeys(elem);
2756 VT100.prototype.showSoftKeyboard = function() {
2757 // Make sure no key is currently selected
2758 this.lastSelectedKey = undefined;
2759 this.deselectKeys(this.keyboard);
2760 this.isShift = false;
2761 this.showShiftState(false);
2762 this.isCtrl = false;
2763 this.showCtrlState(false);
2765 this.showAltState(false);
2767 this.keyboard.style.left = '0px';
2768 this.keyboard.style.top = '0px';
2769 this.keyboard.style.width = this.container.offsetWidth + 'px';
2770 this.keyboard.style.height = this.container.offsetHeight + 'px';
2771 this.keyboard.style.visibility = 'hidden';
2772 this.keyboard.style.display = '';
2774 var kbd = this.keyboard.firstChild;
2776 var transform = this.getTransformName();
2778 kbd.style[transform] = '';
2779 if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2780 scale = (kbd.offsetWidth/
2781 this.container.offsetWidth)/0.9;
2783 if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2784 scale = Math.max((kbd.offsetHeight/
2785 this.container.offsetHeight)/0.9);
2787 var style = this.getTransformStyle(transform,
2788 scale > 1.0 ? scale : undefined);
2789 kbd.style[transform] = style;
2791 if (transform == 'filter') {
2794 kbd.style.left = ((this.container.offsetWidth -
2795 kbd.offsetWidth/scale)/2) + 'px';
2796 kbd.style.top = ((this.container.offsetHeight -
2797 kbd.offsetHeight/scale)/2) + 'px';
2799 this.keyboard.style.visibility = 'visible';
2802 VT100.prototype.hideSoftKeyboard = function() {
2803 this.keyboard.style.display = 'none';
2806 VT100.prototype.toggleCursorBlinking = function() {
2807 this.blinkingCursor = !this.blinkingCursor;
2810 VT100.prototype.about = function() {
2811 alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2812 "\nCopyright 2008-2010 by Markus Gutschke\n" +
2813 "For more information check http://shellinabox.com");
2816 VT100.prototype.hideContextMenu = function() {
2817 this.menu.style.visibility = 'hidden';
2818 this.menu.style.top = '-100px';
2819 this.menu.style.left = '-100px';
2820 this.menu.style.width = '0px';
2821 this.menu.style.height = '0px';
2824 VT100.prototype.extendContextMenu = function(entries, actions) {
2827 VT100.prototype.showContextMenu = function(x, y) {
2828 this.menu.innerHTML =
2829 '<table class="popup" ' +
2830 'cellpadding="0" cellspacing="0">' +
2832 '<ul id="menuentries">' +
2833 '<li id="beginclipboard">Copy</li>' +
2834 '<li id="endclipboard">Paste</li>' +
2835 '<li id="browserclipboard">Paste from browser</li>' +
2837 '<li id="reset">Reset</li>' +
2839 '<li id="beginconfig">' +
2840 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2843 (this.visualBell ? '<img src="enabled.gif" />' : '') +
2846 (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2847 'Onscreen Keyboard</li>' +
2848 '<li id="endconfig">' +
2849 (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2850 'Blinking Cursor</li>'+
2851 (this.usercss.firstChild ?
2852 '<hr id="beginusercss" />' +
2853 this.usercss.innerHTML +
2854 '<hr id="endusercss" />' :
2856 '<li id="about">About...</li>' +
2861 var popup = this.menu.firstChild;
2862 var menuentries = this.getChildById(popup, 'menuentries');
2864 // Determine menu entries that should be disabled
2865 this.lastSelection = this.selection();
2866 if (!this.lastSelection.length) {
2867 menuentries.firstChild.className
2870 var p = this.pasteFnc();
2872 menuentries.childNodes[1].className
2876 // Actions for default items
2877 var actions = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2878 this.toggleUTF, this.toggleBell,
2879 this.toggleSoftKeyboard,
2880 this.toggleCursorBlinking ];
2882 // Actions for user CSS styles (if any)
2883 for (var i = 0; i < this.usercssActions.length; ++i) {
2884 actions[actions.length] = this.usercssActions[i];
2886 actions[actions.length] = this.about;
2888 // Allow subclasses to dynamically add entries to the context menu
2889 this.extendContextMenu(menuentries, actions);
2891 // Hook up event listeners
2892 for (var node = menuentries.firstChild, i = 0; node;
2893 node = node.nextSibling) {
2894 if (node.tagName == 'LI') {
2895 if (node.className != 'disabled') {
2896 this.addListener(node, 'mouseover',
2897 function(vt100, node) {
2899 node.className = 'hover';
2902 this.addListener(node, 'mouseout',
2903 function(vt100, node) {
2905 node.className = '';
2908 this.addListener(node, 'mousedown',
2909 function(vt100, action) {
2910 return function(event) {
2911 vt100.hideContextMenu();
2913 vt100.storeUserSettings();
2914 return vt100.cancelEvent(event || window.event);
2916 }(this, actions[i]));
2917 this.addListener(node, 'mouseup',
2919 return function(event) {
2920 return vt100.cancelEvent(event || window.event);
2923 this.addListener(node, 'mouseclick',
2925 return function(event) {
2926 return vt100.cancelEvent(event || window.event);
2934 // Position menu next to the mouse pointer
2935 this.menu.style.left = '0px';
2936 this.menu.style.top = '0px';
2937 this.menu.style.width = this.container.offsetWidth + 'px';
2938 this.menu.style.height = this.container.offsetHeight + 'px';
2939 popup.style.left = '0px';
2940 popup.style.top = '0px';
2943 if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2944 x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2949 if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2950 y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2955 popup.style.left = x + 'px';
2956 popup.style.top = y + 'px';
2958 // Block all other interactions with the terminal emulator
2959 this.addListener(this.menu, 'click', function(vt100) {
2961 vt100.hideContextMenu();
2966 this.menu.style.visibility = '';
2969 VT100.prototype.keysPressed = function(ch) {
2970 for (var i = 0; i < ch.length; i++) {
2971 var c = ch.charCodeAt(i);
2972 this.vt100(c >= 7 && c <= 15 ||
2973 c == 24 || c == 26 || c == 27 || c >= 32
2974 ? String.fromCharCode(c) : '<' + c + '>');
2978 VT100.prototype.applyModifiers = function(ch, event) {
2980 if (event.ctrlKey) {
2981 if (ch >= 32 && ch <= 127) {
2982 // For historic reasons, some control characters are treated specially
2984 case /* 3 */ 51: ch = 27; break;
2985 case /* 4 */ 52: ch = 28; break;
2986 case /* 5 */ 53: ch = 29; break;
2987 case /* 6 */ 54: ch = 30; break;
2988 case /* 7 */ 55: ch = 31; break;
2989 case /* 8 */ 56: ch = 127; break;
2990 case /* ? */ 63: ch = 127; break;
2991 default: ch &= 31; break;
2995 return String.fromCharCode(ch);
3001 VT100.prototype.handleKey = function(event) {
3002 // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
3003 // (event.shiftKey || event.ctrlKey || event.altKey ||
3004 // event.metaKey ? ', ' +
3005 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3006 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3009 if (typeof event.charCode != 'undefined') {
3010 // non-IE keypress events have a translated charCode value. Also, our
3011 // fake events generated when receiving keydown events include this data
3013 ch = event.charCode;
3014 key = event.keyCode;
3016 // When sending a keypress event, IE includes the translated character
3017 // code in the keyCode field.
3022 // Apply modifier keys (ctrl and shift)
3026 ch = this.applyModifiers(ch, event);
3028 // By this point, "ch" is either defined and contains the character code, or
3029 // it is undefined and "key" defines the code of a function key
3030 if (ch != undefined) {
3031 this.scrollable.scrollTop = this.numScrollbackLines *
3032 this.cursorHeight + 1;
3034 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
3035 // Many programs have difficulties dealing with parametrized escape
3036 // sequences for function keys. Thus, if ALT is the only modifier
3037 // key, return Emacs-style keycodes for commonly used keys.
3039 case 33: /* Page Up */ ch = '\u001B<'; break;
3040 case 34: /* Page Down */ ch = '\u001B>'; break;
3041 case 37: /* Left */ ch = '\u001Bb'; break;
3042 case 38: /* Up */ ch = '\u001Bp'; break;
3043 case 39: /* Right */ ch = '\u001Bf'; break;
3044 case 40: /* Down */ ch = '\u001Bn'; break;
3045 case 46: /* Delete */ ch = '\u001Bd'; break;
3048 } else if (event.shiftKey && !event.ctrlKey &&
3049 !event.altKey && !event.metaKey) {
3051 case 33: /* Page Up */ this.scrollBack(); return;
3052 case 34: /* Page Down */ this.scrollFore(); return;
3056 if (ch == undefined) {
3058 case 8: /* Backspace */ ch = '\u007f'; break;
3059 case 9: /* Tab */ ch = '\u0009'; break;
3060 case 10: /* Return */ ch = '\u000A'; break;
3061 case 13: /* Enter */ ch = this.crLfMode ?
3062 '\r\n' : '\r'; break;
3063 case 16: /* Shift */ return;
3064 case 17: /* Ctrl */ return;
3065 case 18: /* Alt */ return;
3066 case 19: /* Break */ return;
3067 case 20: /* Caps Lock */ return;
3068 case 27: /* Escape */ ch = '\u001B'; break;
3069 case 33: /* Page Up */ ch = '\u001B[5~'; break;
3070 case 34: /* Page Down */ ch = '\u001B[6~'; break;
3071 case 35: /* End */ ch = '\u001BOF'; break;
3072 case 36: /* Home */ ch = '\u001BOH'; break;
3073 case 37: /* Left */ ch = this.cursorKeyMode ?
3074 '\u001BOD' : '\u001B[D'; break;
3075 case 38: /* Up */ ch = this.cursorKeyMode ?
3076 '\u001BOA' : '\u001B[A'; break;
3077 case 39: /* Right */ ch = this.cursorKeyMode ?
3078 '\u001BOC' : '\u001B[C'; break;
3079 case 40: /* Down */ ch = this.cursorKeyMode ?
3080 '\u001BOB' : '\u001B[B'; break;
3081 case 45: /* Insert */ ch = '\u001B[2~'; break;
3082 case 46: /* Delete */ ch = '\u001B[3~'; break;
3083 case 91: /* Left Window */ return;
3084 case 92: /* Right Window */ return;
3085 case 93: /* Select */ return;
3086 case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
3087 case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
3088 case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
3089 case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
3090 case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
3091 case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
3092 case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
3093 case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
3094 case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
3095 case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
3096 case 106: /* * */ ch = this.applyModifiers(42, event); break;
3097 case 107: /* + */ ch = this.applyModifiers(43, event); break;
3098 case 109: /* - */ ch = this.applyModifiers(45, event); break;
3099 case 110: /* . */ ch = this.applyModifiers(46, event); break;
3100 case 111: /* / */ ch = this.applyModifiers(47, event); break;
3101 case 112: /* F1 */ ch = '\u001BOP'; break;
3102 case 113: /* F2 */ ch = '\u001BOQ'; break;
3103 case 114: /* F3 */ ch = '\u001BOR'; break;
3104 case 115: /* F4 */ ch = '\u001BOS'; break;
3105 case 116: /* F5 */ ch = '\u001B[15~'; break;
3106 case 117: /* F6 */ ch = '\u001B[17~'; break;
3107 case 118: /* F7 */ ch = '\u001B[18~'; break;
3108 case 119: /* F8 */ ch = '\u001B[19~'; break;
3109 case 120: /* F9 */ ch = '\u001B[20~'; break;
3110 case 121: /* F10 */ ch = '\u001B[21~'; break;
3111 case 122: /* F11 */ ch = '\u001B[23~'; break;
3112 case 123: /* F12 */ ch = '\u001B[24~'; break;
3113 case 144: /* Num Lock */ return;
3114 case 145: /* Scroll Lock */ return;
3115 case 186: /* ; */ ch = this.applyModifiers(59, event); break;
3116 case 187: /* = */ ch = this.applyModifiers(61, event); break;
3117 case 188: /* , */ ch = this.applyModifiers(44, event); break;
3118 case 189: /* - */ ch = this.applyModifiers(45, event); break;
3119 case 190: /* . */ ch = this.applyModifiers(46, event); break;
3120 case 191: /* / */ ch = this.applyModifiers(47, event); break;
3121 // Conflicts with dead key " on Swiss keyboards
3122 //case 192: /* ` */ ch = this.applyModifiers(96, event); break;
3123 // Conflicts with dead key " on Swiss keyboards
3124 //case 219: /* [ */ ch = this.applyModifiers(91, event); break;
3125 case 220: /* \ */ ch = this.applyModifiers(92, event); break;
3126 // Conflicts with dead key ^ and ` on Swiss keaboards
3127 // ^ and " on French keyboards
3128 //case 221: /* ] */ ch = this.applyModifiers(93, event); break;
3129 case 222: /* ' */ ch = this.applyModifiers(39, event); break;
3132 this.scrollable.scrollTop = this.numScrollbackLines *
3133 this.cursorHeight + 1;
3137 // "ch" now contains the sequence of keycodes to send. But we might still
3138 // have to apply the effects of modifier keys.
3139 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
3140 var start, digit, part1, part2;
3141 if ((start = ch.substr(0, 2)) == '\u001B[') {
3143 part1.length < ch.length &&
3144 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
3145 part1 = ch.substr(0, part1.length + 1);
3147 part2 = ch.substr(part1.length);
3148 if (part1.length > 2) {
3151 } else if (start == '\u001BO') {
3153 part2 = ch.substr(2);
3155 if (part1 != undefined) {
3157 ((event.shiftKey ? 1 : 0) +
3158 (event.altKey|event.metaKey ? 2 : 0) +
3159 (event.ctrlKey ? 4 : 0)) +
3161 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
3166 if (this.menu.style.visibility == 'hidden') {
3167 // this.vt100('R: c=');
3168 // for (var i = 0; i < ch.length; i++)
3169 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
3170 // this.vt100('\r\n');
3171 this.keysPressed(ch);
3175 VT100.prototype.inspect = function(o, d) {
3176 if (d == undefined) {
3180 if (typeof o == 'object' && ++d < 2) {
3183 rc += this.spaces(d * 2) + i + ' -> ';
3185 rc += this.inspect(o[i], d);
3187 rc += '?' + '?' + '?\r\n';
3192 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
3197 VT100.prototype.checkComposedKeys = function(event) {
3198 // Composed keys (at least on Linux) do not generate normal events.
3199 // Instead, they get entered into the text field. We normally catch
3200 // this on the next keyup event.
3201 var s = this.input.value;
3203 this.input.value = '';
3204 if (this.menu.style.visibility == 'hidden') {
3205 this.keysPressed(s);
3210 VT100.prototype.fixEvent = function(event) {
3211 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
3212 // is used as a second-level selector, clear the modifier bits before
3213 // handling the event.
3214 if (event.ctrlKey && event.altKey) {
3216 fake.charCode = event.charCode;
3217 fake.keyCode = event.keyCode;
3218 fake.ctrlKey = false;
3219 fake.shiftKey = event.shiftKey;
3220 fake.altKey = false;
3221 fake.metaKey = event.metaKey;
3225 // Some browsers fail to translate keys, if both shift and alt/meta is
3226 // pressed at the same time. We try to translate those cases, but that
3227 // only works for US keyboard layouts.
3228 if (event.shiftKey) {
3231 switch (this.lastNormalKeyDownEvent.keyCode) {
3232 case 39: /* ' -> " */ u = 39; s = 34; break;
3233 case 44: /* , -> < */ u = 44; s = 60; break;
3234 case 45: /* - -> _ */ u = 45; s = 95; break;
3235 case 46: /* . -> > */ u = 46; s = 62; break;
3236 case 47: /* / -> ? */ u = 47; s = 63; break;
3238 case 48: /* 0 -> ) */ u = 48; s = 41; break;
3239 case 49: /* 1 -> ! */ u = 49; s = 33; break;
3240 case 50: /* 2 -> @ */ u = 50; s = 64; break;
3241 case 51: /* 3 -> # */ u = 51; s = 35; break;
3242 case 52: /* 4 -> $ */ u = 52; s = 36; break;
3243 case 53: /* 5 -> % */ u = 53; s = 37; break;
3244 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
3245 case 55: /* 7 -> & */ u = 55; s = 38; break;
3246 case 56: /* 8 -> * */ u = 56; s = 42; break;
3247 case 57: /* 9 -> ( */ u = 57; s = 40; break;
3249 case 59: /* ; -> : */ u = 59; s = 58; break;
3250 case 61: /* = -> + */ u = 61; s = 43; break;
3251 case 91: /* [ -> { */ u = 91; s = 123; break;
3252 case 92: /* \ -> | */ u = 92; s = 124; break;
3253 case 93: /* ] -> } */ u = 93; s = 125; break;
3254 case 96: /* ` -> ~ */ u = 96; s = 126; break;
3256 case 109: /* - -> _ */ u = 45; s = 95; break;
3257 case 111: /* / -> ? */ u = 47; s = 63; break;
3259 case 186: /* ; -> : */ u = 59; s = 58; break;
3260 case 187: /* = -> + */ u = 61; s = 43; break;
3261 case 188: /* , -> < */ u = 44; s = 60; break;
3262 case 189: /* - -> _ */ u = 45; s = 95; break;
3263 case 190: /* . -> > */ u = 46; s = 62; break;
3264 case 191: /* / -> ? */ u = 47; s = 63; break;
3265 case 192: /* ` -> ~ */ u = 96; s = 126; break;
3266 case 219: /* [ -> { */ u = 91; s = 123; break;
3267 case 220: /* \ -> | */ u = 92; s = 124; break;
3268 case 221: /* ] -> } */ u = 93; s = 125; break;
3269 case 222: /* ' -> " */ u = 39; s = 34; break;
3272 if (s && (event.charCode == u || event.charCode == 0)) {
3275 fake.keyCode = event.keyCode;
3276 fake.ctrlKey = event.ctrlKey;
3277 fake.shiftKey = event.shiftKey;
3278 fake.altKey = event.altKey;
3279 fake.metaKey = event.metaKey;
3286 VT100.prototype.keyDown = function(event) {
3287 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
3288 // (event.shiftKey || event.ctrlKey || event.altKey ||
3289 // event.metaKey ? ', ' +
3290 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3291 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3293 this.checkComposedKeys(event);
3294 this.lastKeyPressedEvent = undefined;
3295 this.lastKeyDownEvent = undefined;
3296 this.lastNormalKeyDownEvent = event;
3298 // Swiss keyboard conflicts:
3305 // French keyoard conflicts:
3309 event.keyCode == 32 ||
3310 event.keyCode >= 48 && event.keyCode <= 57 ||
3311 event.keyCode >= 65 && event.keyCode <= 90;
3314 event.keyCode == 59 ||
3315 event.keyCode >= 96 && event.keyCode <= 105 ||
3316 event.keyCode == 107 ||
3317 event.keyCode == 192 ||
3318 event.keyCode >= 219 && event.keyCode <= 221 ||
3319 event.keyCode == 223 ||
3320 event.keyCode == 226;
3323 event.keyCode == 61 ||
3324 event.keyCode == 106 ||
3325 event.keyCode >= 109 && event.keyCode <= 111 ||
3326 event.keyCode >= 186 && event.keyCode <= 191 ||
3327 event.keyCode == 222 ||
3328 event.keyCode == 252;
3330 if (navigator.appName == 'Konqueror') {
3331 normalKey |= event.keyCode < 128;
3336 // We normally prefer to look at keypress events, as they perform the
3337 // translation from keyCode to charCode. This is important, as the
3338 // translation is locale-dependent.
3339 // But for some keys, we must intercept them during the keydown event,
3340 // as they would otherwise get interpreted by the browser.
3341 // Even, when doing all of this, there are some keys that we can never
3342 // intercept. This applies to some of the menu navigation keys in IE.
3343 // In fact, we see them, but we cannot stop IE from seeing them, too.
3344 if ((event.charCode || event.keyCode) &&
3345 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3347 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3348 // interpret this sequence ourselves, as some keyboard layouts use
3349 // it for second-level layouts.
3350 !(event.ctrlKey && event.altKey)) ||
3351 this.catchModifiersEarly && normalKey && !alphNumKey &&
3352 (event.ctrlKey || event.altKey || event.metaKey) ||
3354 this.lastKeyDownEvent = event;
3356 fake.ctrlKey = event.ctrlKey;
3357 fake.shiftKey = event.shiftKey;
3358 fake.altKey = event.altKey;
3359 fake.metaKey = event.metaKey;
3361 fake.charCode = event.keyCode;
3365 fake.keyCode = event.keyCode;
3366 if (!alphNumKey && event.shiftKey) {
3367 fake = this.fixEvent(fake);
3371 this.handleKey(fake);
3372 this.lastNormalKeyDownEvent = undefined;
3375 // For non-IE browsers
3376 event.stopPropagation();
3377 event.preventDefault();
3382 event.cancelBubble = true;
3383 event.returnValue = false;
3393 VT100.prototype.keyPressed = function(event) {
3394 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3395 // (event.shiftKey || event.ctrlKey || event.altKey ||
3396 // event.metaKey ? ', ' +
3397 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3398 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3400 if (this.lastKeyDownEvent) {
3401 // If we already processed the key on keydown, do not process it
3402 // again here. Ideally, the browser should not even have generated a
3403 // keypress event in this case. But that does not appear to always work.
3404 this.lastKeyDownEvent = undefined;
3406 this.handleKey(event.altKey || event.metaKey
3407 ? this.fixEvent(event) : event);
3411 // For non-IE browsers
3412 event.preventDefault();
3418 event.cancelBubble = true;
3419 event.returnValue = false;
3424 this.lastNormalKeyDownEvent = undefined;
3425 this.lastKeyPressedEvent = event;
3429 VT100.prototype.keyUp = function(event) {
3430 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3431 // (event.shiftKey || event.ctrlKey || event.altKey ||
3432 // event.metaKey ? ', ' +
3433 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3434 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3436 if (this.lastKeyPressedEvent) {
3437 // The compose key on Linux occasionally confuses the browser and keeps
3438 // inserting bogus characters into the input field, even if just a regular
3439 // key has been pressed. Detect this case and drop the bogus characters.
3441 event.srcElement).value = '';
3443 // This is usually were we notice that a key has been composed and
3444 // thus failed to generate normal events.
3445 this.checkComposedKeys(event);
3447 // Some browsers don't report keypress events if ctrl or alt is pressed
3448 // for non-alphanumerical keys. Patch things up for now, but in the
3449 // future we will catch these keys earlier (in the keydown handler).
3450 if (this.lastNormalKeyDownEvent) {
3451 // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3452 this.catchModifiersEarly = true;
3454 event.keyCode == 32 ||
3455 // Conflicts with dead key ~ (code 50) on French keyboards
3456 //event.keyCode >= 48 && event.keyCode <= 57 ||
3457 event.keyCode >= 48 && event.keyCode <= 49 ||
3458 event.keyCode >= 51 && event.keyCode <= 57 ||
3459 event.keyCode >= 65 && event.keyCode <= 90;
3462 event.keyCode == 50 ||
3463 event.keyCode >= 96 && event.keyCode <= 105;
3466 event.keyCode == 59 || event.keyCode == 61 ||
3467 event.keyCode == 106 || event.keyCode == 107 ||
3468 event.keyCode >= 109 && event.keyCode <= 111 ||
3469 event.keyCode >= 186 && event.keyCode <= 192 ||
3470 event.keyCode >= 219 && event.keyCode <= 223 ||
3471 event.keyCode == 252;
3473 fake.ctrlKey = event.ctrlKey;
3474 fake.shiftKey = event.shiftKey;
3475 fake.altKey = event.altKey;
3476 fake.metaKey = event.metaKey;
3478 fake.charCode = event.keyCode;
3482 fake.keyCode = event.keyCode;
3483 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3484 fake = this.fixEvent(fake);
3487 this.lastNormalKeyDownEvent = undefined;
3488 this.handleKey(fake);
3494 event.cancelBubble = true;
3495 event.returnValue = false;
3500 this.lastKeyDownEvent = undefined;
3501 this.lastKeyPressedEvent = undefined;
3505 VT100.prototype.animateCursor = function(inactive) {
3506 if (!this.cursorInterval) {
3507 this.cursorInterval = setInterval(
3510 vt100.animateCursor();
3512 // Use this opportunity to check whether the user entered a composed
3513 // key, or whether somebody pasted text into the textfield.
3514 vt100.checkComposedKeys();
3518 if (inactive != undefined || this.cursor.className != 'inactive') {
3520 this.cursor.className = 'inactive';
3522 if (this.blinkingCursor) {
3523 this.cursor.className = this.cursor.className == 'bright'
3526 this.cursor.className = 'bright';
3532 VT100.prototype.blurCursor = function() {
3533 this.animateCursor(true);
3536 VT100.prototype.focusCursor = function() {
3537 this.animateCursor(false);
3540 VT100.prototype.flashScreen = function() {
3541 this.isInverted = !this.isInverted;
3542 this.refreshInvertedState();
3543 this.isInverted = !this.isInverted;
3544 setTimeout(function(vt100) {
3546 vt100.refreshInvertedState();
3551 VT100.prototype.beep = function() {
3552 if (this.visualBell) {
3559 this.beeper.src = 'beep.wav';
3566 VT100.prototype.bs = function() {
3567 if (this.cursorX > 0) {
3568 this.gotoXY(this.cursorX - 1, this.cursorY);
3569 this.needWrap = false;
3573 VT100.prototype.ht = function(count) {
3574 if (count == undefined) {
3577 var cx = this.cursorX;
3578 while (count-- > 0) {
3579 while (cx++ < this.terminalWidth) {
3580 var tabState = this.userTabStop[cx];
3581 if (tabState == false) {
3582 // Explicitly cleared tab stop
3584 } else if (tabState) {
3585 // Explicitly set tab stop
3588 // Default tab stop at each eighth column
3595 if (cx > this.terminalWidth - 1) {
3596 cx = this.terminalWidth - 1;
3598 if (cx != this.cursorX) {
3599 this.gotoXY(cx, this.cursorY);
3603 VT100.prototype.rt = function(count) {
3604 if (count == undefined) {
3607 var cx = this.cursorX;
3608 while (count-- > 0) {
3610 var tabState = this.userTabStop[cx];
3611 if (tabState == false) {
3612 // Explicitly cleared tab stop
3614 } else if (tabState) {
3615 // Explicitly set tab stop
3618 // Default tab stop at each eighth column
3628 if (cx != this.cursorX) {
3629 this.gotoXY(cx, this.cursorY);
3633 VT100.prototype.cr = function() {
3634 this.gotoXY(0, this.cursorY);
3635 this.needWrap = false;
3638 VT100.prototype.lf = function(count) {
3639 if (count == undefined) {
3642 if (count > this.terminalHeight) {
3643 count = this.terminalHeight;
3649 while (count-- > 0) {
3650 if (this.cursorY == this.bottom - 1) {
3651 this.scrollRegion(0, this.top + 1,
3652 this.terminalWidth, this.bottom - this.top - 1,
3653 0, -1, this.color, this.style);
3655 } else if (this.cursorY < this.terminalHeight - 1) {
3656 this.gotoXY(this.cursorX, this.cursorY + 1);
3661 VT100.prototype.ri = function(count) {
3662 if (count == undefined) {
3665 if (count > this.terminalHeight) {
3666 count = this.terminalHeight;
3672 while (count-- > 0) {
3673 if (this.cursorY == this.top) {
3674 this.scrollRegion(0, this.top,
3675 this.terminalWidth, this.bottom - this.top - 1,
3676 0, 1, this.color, this.style);
3677 } else if (this.cursorY > 0) {
3678 this.gotoXY(this.cursorX, this.cursorY - 1);
3681 this.needWrap = false;
3684 VT100.prototype.respondID = function() {
3685 this.respondString += '\u001B[?6c';
3688 VT100.prototype.respondSecondaryDA = function() {
3689 this.respondString += '\u001B[>0;0;0c';
3693 VT100.prototype.updateStyle = function() {
3695 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3696 this.style = 'text-decoration: underline;';
3698 var bg = (this.attr >> 4) & 0xF;
3699 var fg = this.attr & 0xF;
3700 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3705 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3706 fg = 8; // Dark grey
3707 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3709 this.style = 'font-weight: bold;';
3711 if (this.attr & 0x1000 /* ATTR_BLINK */) {
3712 this.style = 'text-decoration: blink;';
3714 this.color = 'ansi' + fg + ' bgAnsi' + bg;
3717 VT100.prototype.setAttrColors = function(attr) {
3718 if (attr != this.attr) {
3724 VT100.prototype.saveCursor = function() {
3725 this.savedX[this.currentScreen] = this.cursorX;
3726 this.savedY[this.currentScreen] = this.cursorY;
3727 this.savedAttr[this.currentScreen] = this.attr;
3728 this.savedUseGMap = this.useGMap;
3729 for (var i = 0; i < 4; i++) {
3730 this.savedGMap[i] = this.GMap[i];
3732 this.savedValid[this.currentScreen] = true;
3735 VT100.prototype.restoreCursor = function() {
3736 if (!this.savedValid[this.currentScreen]) {
3739 this.attr = this.savedAttr[this.currentScreen];
3741 this.useGMap = this.savedUseGMap;
3742 for (var i = 0; i < 4; i++) {
3743 this.GMap[i] = this.savedGMap[i];
3745 this.translate = this.GMap[this.useGMap];
3746 this.needWrap = false;
3747 this.gotoXY(this.savedX[this.currentScreen],
3748 this.savedY[this.currentScreen]);
3751 VT100.prototype.getTransformName = function() {
3752 var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3753 for (var i = 0; i < styles.length; ++i) {
3754 if (typeof this.console[0].style[styles[i]] != 'undefined') {
3761 VT100.prototype.getTransformStyle = function(transform, scale) {
3762 return scale && scale != 1.0
3763 ? transform == 'filter'
3764 ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3765 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3766 "sizingMethod='auto expand')"
3767 : 'translateX(-50%) ' +
3768 'scaleX(' + (1.0/scale) + ') ' +
3773 VT100.prototype.set80_132Mode = function(state) {
3774 var transform = this.getTransformName();
3776 if ((this.console[this.currentScreen].style[transform] != '') == state) {
3780 this.getTransformStyle(transform, 1.65):'';
3781 this.console[this.currentScreen].style[transform] = style;
3782 this.cursor.style[transform] = style;
3783 this.space.style[transform] = style;
3784 this.scale = state ? 1.65 : 1.0;
3785 if (transform == 'filter') {
3786 this.console[this.currentScreen].style.width = state ? '165%' : '';
3792 VT100.prototype.setMode = function(state) {
3793 for (var i = 0; i <= this.npar; i++) {
3794 if (this.isQuestionMark) {
3795 switch (this.par[i]) {
3796 case 1: this.cursorKeyMode = state; break;
3797 case 3: this.set80_132Mode(state); break;
3798 case 5: this.isInverted = state; this.refreshInvertedState(); break;
3799 case 6: this.offsetMode = state; break;
3800 case 7: this.autoWrapMode = state; break;
3802 case 9: this.mouseReporting = state; break;
3803 case 25: this.cursorNeedsShowing = state;
3804 if (state) { this.showCursor(); }
3805 else { this.hideCursor(); } break;
3808 case 47: this.enableAlternateScreen(state); break;
3812 switch (this.par[i]) {
3813 case 3: this.dispCtrl = state; break;
3814 case 4: this.insertMode = state; break;
3815 case 20:this.crLfMode = state; break;
3822 VT100.prototype.statusReport = function() {
3823 // Ready and operational.
3824 this.respondString += '\u001B[0n';
3827 VT100.prototype.cursorReport = function() {
3828 this.respondString += '\u001B[' +
3829 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3831 (this.cursorX + 1) +
3835 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3836 // Changing of cursor color is not implemented.
3839 VT100.prototype.openPrinterWindow = function() {
3842 if (!this.printWin || this.printWin.closed) {
3843 this.printWin = window.open('', 'print-output',
3844 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3845 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3846 this.printWin.document.body.innerHTML =
3847 '<link rel="stylesheet" href="' +
3848 document.location.protocol + '//' + document.location.host +
3849 document.location.pathname.replace(/[^/]*$/, '') +
3850 'print-styles.css" type="text/css">\n' +
3851 '<div id="options"><input id="autoprint" type="checkbox"' +
3852 (this.autoprint ? ' checked' : '') + '>' +
3853 'Automatically, print page(s) when job is ready' +
3854 '</input></div>\n' +
3855 '<div id="spacer"><input type="checkbox"> </input></div>' +
3856 '<pre id="print"></pre>\n';
3857 var autoprint = this.printWin.document.getElementById('autoprint');
3858 this.addListener(autoprint, 'click',
3859 (function(vt100, autoprint) {
3861 vt100.autoprint = autoprint.checked;
3862 vt100.storeUserSettings();
3865 })(this, autoprint));
3866 this.printWin.document.title = 'ShellInABox Printer Output';
3869 // Maybe, a popup blocker prevented us from working. Better catch the
3870 // exception, so that we won't break the entire terminal session. The
3871 // user probably needs to disable the blocker first before retrying the
3875 rc &= this.printWin && !this.printWin.closed &&
3876 (this.printWin.innerWidth ||
3877 this.printWin.document.documentElement.clientWidth ||
3878 this.printWin.document.body.clientWidth) > 1;
3880 if (!rc && this.printing == 100) {
3881 // Different popup blockers work differently. We try to detect a couple
3882 // of common methods. And then we retry again a brief amount later, as
3883 // false positives are otherwise possible. If we are sure that there is
3884 // a popup blocker in effect, we alert the user to it. This is helpful
3885 // as some popup blockers have minimal or no UI, and the user might not
3886 // notice that they are missing the popup. In any case, we only show at
3887 // most one message per print job.
3888 this.printing = true;
3889 setTimeout((function(win) {
3891 if (!win || win.closed ||
3893 win.document.documentElement.clientWidth ||
3894 win.document.body.clientWidth) <= 1) {
3895 alert('Attempted to print, but a popup blocker ' +
3896 'prevented the printer window from opening');
3899 })(this.printWin), 2000);
3904 VT100.prototype.sendToPrinter = function(s) {
3905 this.openPrinterWindow();
3907 var doc = this.printWin.document;
3908 var print = doc.getElementById('print');
3909 if (print.lastChild && print.lastChild.nodeName == '#text') {
3910 print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3912 print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3915 // There probably was a more aggressive popup blocker that prevented us
3916 // from accessing the printer windows.
3920 VT100.prototype.sendControlToPrinter = function(ch) {
3921 // We get called whenever doControl() is active. But for the printer, we
3922 // only implement a basic line printer that doesn't understand most of
3923 // the escape sequences of the VT100 terminal. In fact, the only escape
3924 // sequence that we really need to recognize is '^[[5i' for turning the
3930 this.openPrinterWindow();
3931 var doc = this.printWin.document;
3932 var print = doc.getElementById('print');
3933 var chars = print.lastChild &&
3934 print.lastChild.nodeName == '#text' ?
3935 print.lastChild.textContent.length : 0;
3936 this.sendToPrinter(this.spaces(8 - (chars % 8)));
3943 this.openPrinterWindow();
3944 var pageBreak = this.printWin.document.createElement('div');
3945 pageBreak.className = 'pagebreak';
3946 pageBreak.innerHTML = '<hr />';
3947 this.printWin.document.getElementById('print').appendChild(pageBreak);
3951 this.openPrinterWindow();
3952 var lineBreak = this.printWin.document.createElement('br');
3953 this.printWin.document.getElementById('print').appendChild(lineBreak);
3957 this.isEsc = 1 /* ESesc */;
3960 switch (this.isEsc) {
3962 this.isEsc = 0 /* ESnormal */;
3965 this.isEsc = 2 /* ESsquare */;
3971 case 2 /* ESsquare */:
3973 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3974 0, 0, 0, 0, 0, 0, 0, 0 ];
3975 this.isEsc = 3 /* ESgetpars */;
3976 this.isQuestionMark = ch == 0x3F /*?*/;
3977 if (this.isQuestionMark) {
3981 case 3 /* ESgetpars */:
3982 if (ch == 0x3B /*;*/) {
3985 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3986 var par = this.par[this.npar];
3987 if (par == undefined) {
3990 this.par[this.npar] = 10*par + (ch & 0xF);
3993 this.isEsc = 4 /* ESgotpars */;
3996 case 4 /* ESgotpars */:
3997 this.isEsc = 0 /* ESnormal */;
3998 if (this.isQuestionMark) {
4003 this.csii(this.par[0]);
4010 this.isEsc = 0 /* ESnormal */;
4016 // There probably was a more aggressive popup blocker that prevented us
4017 // from accessing the printer windows.
4021 VT100.prototype.csiAt = function(number) {
4026 if (number > this.terminalWidth - this.cursorX) {
4027 number = this.terminalWidth - this.cursorX;
4029 this.scrollRegion(this.cursorX, this.cursorY,
4030 this.terminalWidth - this.cursorX - number, 1,
4031 number, 0, this.color, this.style);
4032 this.needWrap = false;
4035 VT100.prototype.csii = function(number) {
4038 case 0: // Print Screen
4041 case 4: // Stop printing
4043 if (this.printing && this.printWin && !this.printWin.closed) {
4044 var print = this.printWin.document.getElementById('print');
4045 while (print.lastChild &&
4046 print.lastChild.tagName == 'DIV' &&
4047 print.lastChild.className == 'pagebreak') {
4048 // Remove trailing blank pages
4049 print.removeChild(print.lastChild);
4051 if (this.autoprint) {
4052 this.printWin.print();
4057 this.printing = false;
4059 case 5: // Start printing
4060 if (!this.printing && this.printWin && !this.printWin.closed) {
4061 this.printWin.document.getElementById('print').innerHTML = '';
4063 this.printing = 100;
4070 VT100.prototype.csiJ = function(number) {
4072 case 0: // Erase from cursor to end of display
4073 this.clearRegion(this.cursorX, this.cursorY,
4074 this.terminalWidth - this.cursorX, 1,
4075 this.color, this.style);
4076 if (this.cursorY < this.terminalHeight-2) {
4077 this.clearRegion(0, this.cursorY+1,
4078 this.terminalWidth, this.terminalHeight-this.cursorY-1,
4079 this.color, this.style);
4082 case 1: // Erase from start to cursor
4083 if (this.cursorY > 0) {
4084 this.clearRegion(0, 0,
4085 this.terminalWidth, this.cursorY,
4086 this.color, this.style);
4088 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4089 this.color, this.style);
4091 case 2: // Erase whole display
4092 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
4093 this.color, this.style);
4101 VT100.prototype.csiK = function(number) {
4103 case 0: // Erase from cursor to end of line
4104 this.clearRegion(this.cursorX, this.cursorY,
4105 this.terminalWidth - this.cursorX, 1,
4106 this.color, this.style);
4108 case 1: // Erase from start of line to cursor
4109 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4110 this.color, this.style);
4112 case 2: // Erase whole line
4113 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
4114 this.color, this.style);
4122 VT100.prototype.csiL = function(number) {
4123 // Open line by inserting blank line(s)
4124 if (this.cursorY >= this.bottom) {
4130 if (number > this.bottom - this.cursorY) {
4131 number = this.bottom - this.cursorY;
4133 this.scrollRegion(0, this.cursorY,
4134 this.terminalWidth, this.bottom - this.cursorY - number,
4135 0, number, this.color, this.style);
4139 VT100.prototype.csiM = function(number) {
4140 // Delete line(s), scrolling up the bottom of the screen.
4141 if (this.cursorY >= this.bottom) {
4147 if (number > this.bottom - this.cursorY) {
4148 number = bottom - cursorY;
4150 this.scrollRegion(0, this.cursorY + number,
4151 this.terminalWidth, this.bottom - this.cursorY - number,
4152 0, -number, this.color, this.style);
4156 VT100.prototype.csim = function() {
4157 for (var i = 0; i <= this.npar; i++) {
4158 switch (this.par[i]) {
4159 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
4160 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
4161 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
4162 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
4163 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
4164 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
4166 this.translate = this.GMap[this.useGMap];
4167 this.dispCtrl = false;
4168 this.toggleMeta = false;
4171 this.translate = this.CodePage437Map;
4172 this.dispCtrl = true;
4173 this.toggleMeta = false;
4176 this.translate = this.CodePage437Map;
4177 this.dispCtrl = true;
4178 this.toggleMeta = true;
4181 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
4182 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
4183 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
4184 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
4185 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
4186 0x0200 /* ATTR_UNDERLINE */; break;
4187 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
4188 case 49: this.attr |= 0xF0; break;
4190 if (this.par[i] >= 30 && this.par[i] <= 37) {
4191 var fg = this.par[i] - 30;
4192 this.attr = (this.attr & ~0x0F) | fg;
4193 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
4194 var bg = this.par[i] - 40;
4195 this.attr = (this.attr & ~0xF0) | (bg << 4);
4203 VT100.prototype.csiP = function(number) {
4204 // Delete character(s) following cursor
4208 if (number > this.terminalWidth - this.cursorX) {
4209 number = this.terminalWidth - this.cursorX;
4211 this.scrollRegion(this.cursorX + number, this.cursorY,
4212 this.terminalWidth - this.cursorX - number, 1,
4213 -number, 0, this.color, this.style);
4217 VT100.prototype.csiX = function(number) {
4218 // Clear characters following cursor
4222 if (number > this.terminalWidth - this.cursorX) {
4223 number = this.terminalWidth - this.cursorX;
4225 this.clearRegion(this.cursorX, this.cursorY, number, 1,
4226 this.color, this.style);
4230 VT100.prototype.settermCommand = function() {
4231 // Setterm commands are not implemented
4234 VT100.prototype.doControl = function(ch) {
4235 if (this.printing) {
4236 this.sendControlToPrinter(ch);
4241 case 0x00: /* ignored */ break;
4242 case 0x08: this.bs(); break;
4243 case 0x09: this.ht(); break;
4247 case 0x84: this.lf(); if (!this.crLfMode) break;
4248 case 0x0D: this.cr(); break;
4249 case 0x85: this.cr(); this.lf(); break;
4250 case 0x0E: this.useGMap = 1;
4251 this.translate = this.GMap[1];
4252 this.dispCtrl = true; break;
4253 case 0x0F: this.useGMap = 0;
4254 this.translate = this.GMap[0];
4255 this.dispCtrl = false; break;
4257 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
4258 case 0x1B: this.isEsc = 1 /* ESesc */; break;
4259 case 0x7F: /* ignored */ break;
4260 case 0x88: this.userTabStop[this.cursorX] = true; break;
4261 case 0x8D: this.ri(); break;
4262 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
4263 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
4264 case 0x9A: this.respondID(); break;
4265 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
4266 case 0x07: if (this.isEsc != 17 /* EStitle */) {
4270 default: switch (this.isEsc) {
4272 this.isEsc = 0 /* ESnormal */;
4274 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
4275 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
4277 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
4279 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
4281 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
4282 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
4283 /*7*/ case 0x37: this.saveCursor(); break;
4284 /*8*/ case 0x38: this.restoreCursor(); break;
4285 /*>*/ case 0x3E: this.applKeyMode = false; break;
4286 /*=*/ case 0x3D: this.applKeyMode = true; break;
4287 /*D*/ case 0x44: this.lf(); break;
4288 /*E*/ case 0x45: this.cr(); this.lf(); break;
4289 /*M*/ case 0x4D: this.ri(); break;
4290 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
4291 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
4292 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
4293 /*Z*/ case 0x5A: this.respondID(); break;
4294 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
4295 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
4296 /*c*/ case 0x63: this.reset(); break;
4297 /*g*/ case 0x67: this.flashScreen(); break;
4301 case 15 /* ESnonstd */:
4305 /*2*/ case 0x32: this.isEsc = 17 /* EStitle */; this.titleString = ''; break;
4306 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4307 this.isEsc = 16 /* ESpalette */; break;
4308 /*R*/ case 0x52: // Palette support is not implemented
4309 this.isEsc = 0 /* ESnormal */; break;
4310 default: this.isEsc = 0 /* ESnormal */; break;
4313 case 16 /* ESpalette */:
4314 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4315 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4316 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4317 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
4319 if (this.npar == 7) {
4320 // Palette support is not implemented
4321 this.isEsc = 0 /* ESnormal */;
4324 this.isEsc = 0 /* ESnormal */;
4327 case 2 /* ESsquare */:
4329 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
4330 0, 0, 0, 0, 0, 0, 0, 0 ];
4331 this.isEsc = 3 /* ESgetpars */;
4332 /*[*/ if (ch == 0x5B) { // Function key
4333 this.isEsc = 6 /* ESfunckey */;
4336 /*?*/ this.isQuestionMark = ch == 0x3F;
4337 if (this.isQuestionMark) {
4342 case 5 /* ESdeviceattr */:
4343 case 3 /* ESgetpars */:
4344 /*;*/ if (ch == 0x3B) {
4347 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4348 var par = this.par[this.npar];
4349 if (par == undefined) {
4352 this.par[this.npar] = 10*par + (ch & 0xF);
4354 } else if (this.isEsc == 5 /* ESdeviceattr */) {
4356 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
4357 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
4358 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
4359 /*p*/ case 0x70: /* set pointer mode resource value */ break;
4362 this.isEsc = 0 /* ESnormal */;
4365 this.isEsc = 4 /* ESgotpars */;
4368 case 4 /* ESgotpars */:
4369 this.isEsc = 0 /* ESnormal */;
4370 if (this.isQuestionMark) {
4372 /*h*/ case 0x68: this.setMode(true); break;
4373 /*l*/ case 0x6C: this.setMode(false); break;
4374 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
4377 this.isQuestionMark = false;
4381 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
4382 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
4384 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
4385 /*A*/ case 0x41: this.gotoXY(this.cursorX,
4386 this.cursorY - (this.par[0] ? this.par[0] : 1));
4389 /*e*/ case 0x65: this.gotoXY(this.cursorX,
4390 this.cursorY + (this.par[0] ? this.par[0] : 1));
4393 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4394 this.cursorY); break;
4395 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4396 this.cursorY); break;
4397 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4399 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4401 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
4403 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
4404 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
4405 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
4406 /*i*/ case 0x69: this.csii(this.par[0]); break;
4407 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
4408 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
4409 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
4410 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
4411 /*m*/ case 0x6D: this.csim(); break;
4412 /*P*/ case 0x50: this.csiP(this.par[0]); break;
4413 /*X*/ case 0x58: this.csiX(this.par[0]); break;
4414 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4415 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4416 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4417 /*g*/ case 0x67: if (this.par[0] == 0) {
4418 this.userTabStop[this.cursorX] = false;
4419 } else if (this.par[0] == 2 || this.par[0] == 3) {
4420 this.userTabStop = [ ];
4421 for (var i = 0; i < this.terminalWidth; i++) {
4422 this.userTabStop[i] = false;
4426 /*h*/ case 0x68: this.setMode(true); break;
4427 /*l*/ case 0x6C: this.setMode(false); break;
4428 /*n*/ case 0x6E: switch (this.par[0]) {
4429 case 5: this.statusReport(); break;
4430 case 6: this.cursorReport(); break;
4434 /*q*/ case 0x71: // LED control not implemented
4436 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4437 var b = this.par[1] ? this.par[1]
4438 : this.terminalHeight;
4439 if (t < b && b <= this.terminalHeight) {
4445 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4446 if (c > this.terminalWidth * this.terminalHeight) {
4447 c = this.terminalWidth * this.terminalHeight;
4450 lineBuf += this.lastCharacter;
4453 /*s*/ case 0x73: this.saveCursor(); break;
4454 /*u*/ case 0x75: this.restoreCursor(); break;
4455 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4456 /*]*/ case 0x5D: this.settermCommand(); break;
4460 case 12 /* ESbang */:
4464 this.isEsc = 0 /* ESnormal */;
4466 case 13 /* ESpercent */:
4467 this.isEsc = 0 /* ESnormal */;
4469 /*@*/ case 0x40: this.utfEnabled = false; break;
4471 /*8*/ case 0x38: this.utfEnabled = true; break;
4475 case 6 /* ESfunckey */:
4476 this.isEsc = 0 /* ESnormal */; break;
4477 case 7 /* EShash */:
4478 this.isEsc = 0 /* ESnormal */;
4479 /*8*/ if (ch == 0x38) {
4480 // Screen alignment test not implemented
4483 case 8 /* ESsetG0 */:
4484 case 9 /* ESsetG1 */:
4485 case 10 /* ESsetG2 */:
4486 case 11 /* ESsetG3 */:
4487 var g = this.isEsc - 8 /* ESsetG0 */;
4488 this.isEsc = 0 /* ESnormal */;
4490 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4492 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4493 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4494 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4497 if (this.useGMap == g) {
4498 this.translate = this.GMap[g];
4501 case 17 /* EStitle */:
4503 if (this.titleString && this.titleString.charAt(0) == ';') {
4504 this.titleString = this.titleString.substr(1);
4505 if (this.titleString != '') {
4506 this.titleString += ' - ';
4508 this.titleString += 'Shell In A Box'
4511 window.document.title = this.titleString;
4514 this.isEsc = 0 /* ESnormal */;
4516 this.titleString += String.fromCharCode(ch);
4519 case 18 /* ESss2 */:
4520 case 19 /* ESss3 */:
4522 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4523 [this.toggleMeta ? (ch | 0x80) : ch];
4524 if ((ch & 0xFF00) == 0xF000) {
4526 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4527 this.isEsc = 0 /* ESnormal */; break;
4530 this.lastCharacter = String.fromCharCode(ch);
4531 lineBuf += this.lastCharacter;
4532 this.isEsc = 0 /* ESnormal */; break;
4534 this.isEsc = 0 /* ESnormal */; break;
4541 VT100.prototype.renderString = function(s, showCursor) {
4542 if (this.printing) {
4543 this.sendToPrinter(s);
4550 // We try to minimize the number of DOM operations by coalescing individual
4551 // characters into strings. This is a significant performance improvement.
4552 var incX = s.length;
4553 if (incX > this.terminalWidth - this.cursorX) {
4554 incX = this.terminalWidth - this.cursorX;
4558 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4561 // Minimize the number of calls to putString(), by avoiding a direct
4562 // call to this.showCursor()
4563 this.cursor.style.visibility = '';
4565 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4568 VT100.prototype.vt100 = function(s) {
4569 this.cursorNeedsShowing = this.hideCursor();
4570 this.respondString = '';
4572 for (var i = 0; i < s.length; i++) {
4573 var ch = s.charCodeAt(i);
4574 if (this.utfEnabled) {
4575 // Decode UTF8 encoded character
4577 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4578 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4579 if (--this.utfCount <= 0) {
4580 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4589 if ((ch & 0xE0) == 0xC0) {
4591 this.utfChar = ch & 0x1F;
4592 } else if ((ch & 0xF0) == 0xE0) {
4594 this.utfChar = ch & 0x0F;
4595 } else if ((ch & 0xF8) == 0xF0) {
4597 this.utfChar = ch & 0x07;
4598 } else if ((ch & 0xFC) == 0xF8) {
4600 this.utfChar = ch & 0x03;
4601 } else if ((ch & 0xFE) == 0xFC) {
4603 this.utfChar = ch & 0x01;
4613 var isNormalCharacter =
4614 (ch >= 32 && ch <= 127 || ch >= 160 ||
4615 this.utfEnabled && ch >= 128 ||
4616 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4617 (ch != 0x7F || this.dispCtrl);
4619 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4621 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4623 if ((ch & 0xFF00) == 0xF000) {
4625 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4628 if (!this.printing) {
4629 if (this.needWrap || this.insertMode) {
4631 this.renderString(lineBuf);
4635 if (this.needWrap) {
4636 this.cr(); this.lf();
4638 if (this.insertMode) {
4639 this.scrollRegion(this.cursorX, this.cursorY,
4640 this.terminalWidth - this.cursorX - 1, 1,
4641 1, 0, this.color, this.style);
4644 this.lastCharacter = String.fromCharCode(ch);
4645 lineBuf += this.lastCharacter;
4646 if (!this.printing &&
4647 this.cursorX + lineBuf.length >= this.terminalWidth) {
4648 this.needWrap = this.autoWrapMode;
4652 this.renderString(lineBuf);
4655 var expand = this.doControl(ch);
4656 if (expand.length) {
4657 var r = this.respondString;
4658 this.respondString= r + this.vt100(expand);
4663 this.renderString(lineBuf, this.cursorNeedsShowing);
4664 } else if (this.cursorNeedsShowing) {
4667 return this.respondString;
4670 VT100.prototype.Latin1Map = [
4671 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4672 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4673 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4674 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4675 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4676 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4677 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4678 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4679 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4680 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4681 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4682 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4683 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4684 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4685 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4686 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4687 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4688 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4689 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4690 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4691 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4692 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4693 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4694 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4695 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4696 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4697 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4698 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4699 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4700 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4701 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4702 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4705 VT100.prototype.VT100GraphicsMap = [
4706 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4707 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4708 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4709 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4710 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4711 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4712 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4713 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4714 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4715 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4716 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4717 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4718 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4719 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4720 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4721 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4722 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4723 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4724 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4725 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4726 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4727 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4728 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4729 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4730 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4731 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4732 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4733 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4734 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4735 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4736 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4737 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4740 VT100.prototype.CodePage437Map = [
4741 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4742 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4743 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4744 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4745 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4746 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4747 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4748 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4749 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4750 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4751 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4752 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4753 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4754 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4755 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4756 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4757 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4758 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4759 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4760 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4761 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4762 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4763 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4764 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4765 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4766 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4767 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4768 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4769 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4770 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4771 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4772 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4775 VT100.prototype.DirectToFontMap = [
4776 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4777 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4778 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4779 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4780 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4781 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4782 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4783 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4784 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4785 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4786 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4787 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4788 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4789 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4790 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4791 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4792 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4793 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4794 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4795 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4796 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4797 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4798 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4799 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4800 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4801 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4802 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4803 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4804 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4805 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4806 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4807 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4810 VT100.prototype.ctrlAction = [
4811 true, false, false, false, false, false, false, true,
4812 true, true, true, true, true, true, true, true,
4813 false, false, false, false, false, false, false, false,
4814 true, false, true, true, false, false, false, false
4817 VT100.prototype.ctrlAlways = [
4818 true, false, false, false, false, false, false, false,
4819 true, false, true, false, true, true, true, true,
4820 false, false, false, false, false, false, false, false,
4821 false, false, false, true, false, false, false, false