Fix 2.4.2 upgrade notes formatting refs #19330
[arvados.git] / apps / workbench / lib / assets / javascripts / webshell / shell_in_a_box.js
1 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com> All rights reserved.
2 //
3 // SPDX-License-Identifier: GPL-2.0
4
5 // This file contains code from shell_in_a_box.js and vt100.js
6
7
8 // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
9 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
10 //
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License version 2 as
13 // published by the Free Software Foundation.
14 //
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License along
21 // with this program; if not, write to the Free Software Foundation, Inc.,
22 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 //
24 // In addition to these license terms, the author grants the following
25 // additional rights:
26 //
27 // If you modify this program, or any covered work, by linking or
28 // combining it with the OpenSSL project's OpenSSL library (or a
29 // modified version of that library), containing parts covered by the
30 // terms of the OpenSSL or SSLeay licenses, the author
31 // grants you additional permission to convey the resulting work.
32 // Corresponding Source for a non-source form of such a combination
33 // shall include the source code for the parts of OpenSSL used as well
34 // as that of the covered work.
35 //
36 // You may at your option choose to remove this additional permission from
37 // the work, or from any part of it.
38 //
39 // It is possible to build this program in a way that it loads OpenSSL
40 // libraries at run-time. If doing so, the following notices are required
41 // by the OpenSSL and SSLeay licenses:
42 //
43 // This product includes software developed by the OpenSSL Project
44 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
45 //
46 // This product includes cryptographic software written by Eric Young
47 // (eay@cryptsoft.com)
48 //
49 //
50 // The most up-to-date version of this program is always available from
51 // http://shellinabox.com
52 //
53 //
54 // Notes:
55 //
56 // The author believes that for the purposes of this license, you meet the
57 // requirements for publishing the source code, if your web server publishes
58 // the source in unmodified form (i.e. with licensing information, comments,
59 // formatting, and identifier names intact). If there are technical reasons
60 // that require you to make changes to the source code when serving the
61 // JavaScript (e.g to remove pre-processor directives from the source), these
62 // changes should be done in a reversible fashion.
63 //
64 // The author does not consider websites that reference this script in
65 // unmodified form, and web servers that serve this script in unmodified form
66 // to be derived works. As such, they are believed to be outside of the
67 // scope of this license and not subject to the rights or restrictions of the
68 // GNU General Public License.
69 //
70 // If in doubt, consult a legal professional familiar with the laws that
71 // apply in your country.
72
73 // #define XHR_UNITIALIZED 0
74 // #define XHR_OPEN        1
75 // #define XHR_SENT        2
76 // #define XHR_RECEIVING   3
77 // #define XHR_LOADED      4
78
79 // IE does not define XMLHttpRequest by default, so we provide a suitable
80 // wrapper.
81 if (typeof XMLHttpRequest == 'undefined') {
82   XMLHttpRequest = function() {
83     try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
84     try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
85     try { return new ActiveXObject('Msxml2.XMLHTTP');    } catch (e) { }
86     try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
87     throw new Error('');
88   };
89 }
90
91 function extend(subClass, baseClass) {
92   function inheritance() { }
93   inheritance.prototype          = baseClass.prototype;
94   subClass.prototype             = new inheritance();
95   subClass.prototype.constructor = subClass;
96   subClass.prototype.superClass  = baseClass.prototype;
97 };
98
99 function ShellInABox(url, container) {
100   if (url == undefined) {
101     this.rooturl    = document.location.href;
102     this.url        = document.location.href.replace(/[?#].*/, '');
103   } else {
104     this.rooturl    = url;
105     this.url        = url;
106   }
107   if (document.location.hash != '') {
108     var hash        = decodeURIComponent(document.location.hash).
109                       replace(/^#/, '');
110     this.nextUrl    = hash.replace(/,.*/, '');
111     this.session    = hash.replace(/[^,]*,/, '');
112   } else {
113     this.nextUrl    = this.url;
114     this.session    = null;
115   }
116   this.pendingKeys  = '';
117   this.keysInFlight = false;
118   this.connected    = false;
119   this.superClass.constructor.call(this, container);
120
121   // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
122   // Chrome never realizes that the page has loaded.
123   setTimeout(function(shellInABox) {
124                return function() {
125                  shellInABox.sendRequest();
126                };
127              }(this), 1);
128 };
129 extend(ShellInABox, VT100);
130
131 ShellInABox.prototype.sessionClosed = function() {
132   try {
133     this.connected    = false;
134     if (this.session) {
135       this.session    = undefined;
136       if (this.cursorX > 0) {
137         this.vt100('\r\n');
138       }
139       this.vt100('Session closed.');
140     }
141     // Revealing the "reconnect" button is commented out until we hook
142     // up the username+token auto-login mechanism to the new session:
143     //this.showReconnect(true);
144   } catch (e) {
145   }
146 };
147
148 ShellInABox.prototype.reconnect = function() {
149   this.showReconnect(false);
150   if (!this.session) {
151     if (document.location.hash != '') {
152       // A shellinaboxd daemon launched from a CGI only allows a single
153       // session. In order to reconnect, we must reload the frame definition
154       // and obtain a new port number. As this is a different origin, we
155       // need to get enclosing page to help us.
156       parent.location        = this.nextUrl;
157     } else {
158       if (this.url != this.nextUrl) {
159         document.location.replace(this.nextUrl);
160       } else {
161         this.pendingKeys     = '';
162         this.keysInFlight    = false;
163         this.reset(true);
164         this.sendRequest();
165       }
166     }
167   }
168   return false;
169 };
170
171 ShellInABox.prototype.sendRequest = function(request) {
172   if (request == undefined) {
173     request                  = new XMLHttpRequest();
174   }
175   request.open('POST', this.url + '?', true);
176   request.setRequestHeader('Cache-Control', 'no-cache');
177   request.setRequestHeader('Content-Type',
178                            'application/x-www-form-urlencoded; charset=utf-8');
179   var content                = 'width=' + this.terminalWidth +
180                                '&height=' + this.terminalHeight +
181                                (this.session ? '&session=' +
182                                 encodeURIComponent(this.session) : '&rooturl='+
183                                 encodeURIComponent(this.rooturl));
184
185   request.onreadystatechange = function(shellInABox) {
186     return function() {
187              try {
188                return shellInABox.onReadyStateChange(request);
189              } catch (e) {
190                shellInABox.sessionClosed();
191              }
192            }
193     }(this);
194   ShellInABox.lastRequestSent = Date.now();
195   request.send(content);
196 };
197
198 ShellInABox.prototype.onReadyStateChange = function(request) {
199   if (request.readyState == 4 /* XHR_LOADED */) {
200     if (request.status == 200) {
201       this.connected = true;
202       var response   = eval('(' + request.responseText + ')');
203       if (response.data) {
204         this.vt100(response.data);
205       }
206
207       if (!response.session ||
208           this.session && this.session != response.session) {
209         this.sessionClosed();
210       } else {
211         this.session = response.session;
212         this.sendRequest(request);
213       }
214     } else if (request.status == 0) {
215         if (ShellInABox.lastRequestSent + 2000 < Date.now()) {
216             // Timeout, try again
217             this.sendRequest(request);
218         } else {
219             this.vt100('\r\n\r\nRequest failed.');
220             this.sessionClosed();
221         }
222     } else {
223       this.sessionClosed();
224     }
225   }
226 };
227
228 ShellInABox.prototype.sendKeys = function(keys) {
229   if (!this.connected) {
230     return;
231   }
232   if (this.keysInFlight || this.session == undefined) {
233     this.pendingKeys          += keys;
234   } else {
235     this.keysInFlight          = true;
236     keys                       = this.pendingKeys + keys;
237     this.pendingKeys           = '';
238     var request                = new XMLHttpRequest();
239     request.open('POST', this.url + '?', true);
240     request.setRequestHeader('Cache-Control', 'no-cache');
241     request.setRequestHeader('Content-Type',
242                            'application/x-www-form-urlencoded; charset=utf-8');
243     var content                = 'width=' + this.terminalWidth +
244                                  '&height=' + this.terminalHeight +
245                                  '&session=' +encodeURIComponent(this.session)+
246                                  '&keys=' + encodeURIComponent(keys);
247     request.onreadystatechange = function(shellInABox) {
248       return function() {
249                try {
250                  return shellInABox.keyPressReadyStateChange(request);
251                } catch (e) {
252                }
253              }
254       }(this);
255     request.send(content);
256   }
257 };
258
259 ShellInABox.prototype.keyPressReadyStateChange = function(request) {
260   if (request.readyState == 4 /* XHR_LOADED */) {
261     this.keysInFlight = false;
262     if (this.pendingKeys) {
263       this.sendKeys('');
264     }
265   }
266 };
267
268 ShellInABox.prototype.keysPressed = function(ch) {
269   var hex = '0123456789ABCDEF';
270   var s   = '';
271   for (var i = 0; i < ch.length; i++) {
272     var c = ch.charCodeAt(i);
273     if (c < 128) {
274       s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
275     } else if (c < 0x800) {
276       s += hex.charAt(0xC +  (c >> 10)       ) +
277            hex.charAt(       (c >>  6) & 0xF ) +
278            hex.charAt(0x8 + ((c >>  4) & 0x3)) +
279            hex.charAt(        c        & 0xF );
280     } else if (c < 0x10000) {
281       s += 'E'                                 +
282            hex.charAt(       (c >> 12)       ) +
283            hex.charAt(0x8 + ((c >> 10) & 0x3)) +
284            hex.charAt(       (c >>  6) & 0xF ) +
285            hex.charAt(0x8 + ((c >>  4) & 0x3)) +
286            hex.charAt(        c        & 0xF );
287     } else if (c < 0x110000) {
288       s += 'F'                                 +
289            hex.charAt(       (c >> 18)       ) +
290            hex.charAt(0x8 + ((c >> 16) & 0x3)) +
291            hex.charAt(       (c >> 12) & 0xF ) +
292            hex.charAt(0x8 + ((c >> 10) & 0x3)) +
293            hex.charAt(       (c >>  6) & 0xF ) +
294            hex.charAt(0x8 + ((c >>  4) & 0x3)) +
295            hex.charAt(        c        & 0xF );
296     }
297   }
298   this.sendKeys(s);
299 };
300
301 ShellInABox.prototype.resized = function(w, h) {
302   // Do not send a resize request until we are fully initialized.
303   if (this.session) {
304     // sendKeys() always transmits the current terminal size. So, flush all
305     // pending keys.
306     this.sendKeys('');
307   }
308 };
309
310 ShellInABox.prototype.toggleSSL = function() {
311   if (document.location.hash != '') {
312     if (this.nextUrl.match(/\?plain$/)) {
313       this.nextUrl    = this.nextUrl.replace(/\?plain$/, '');
314     } else {
315       this.nextUrl    = this.nextUrl.replace(/[?#].*/, '') + '?plain';
316     }
317     if (!this.session) {
318       parent.location = this.nextUrl;
319     }
320   } else {
321     this.nextUrl      = this.nextUrl.match(/^https:/)
322            ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
323            : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
324   }
325   if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
326     this.nextUrl     += '/';
327   }
328   if (this.session && this.nextUrl != this.url) {
329     alert('This change will take effect the next time you login.');
330   }
331 };
332
333 ShellInABox.prototype.extendContextMenu = function(entries, actions) {
334   // Modify the entries and actions in place, adding any locally defined
335   // menu entries.
336   var oldActions            = [ ];
337   for (var i = 0; i < actions.length; i++) {
338     oldActions[i]           = actions[i];
339   }
340   for (var node = entries.firstChild, i = 0, j = 0; node;
341        node = node.nextSibling) {
342     if (node.tagName == 'LI') {
343       actions[i++]          = oldActions[j++];
344       if (node.id == "endconfig") {
345         node.id             = '';
346         if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
347             !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
348           // If the server supports both SSL and plain text connections,
349           // provide a menu entry to switch between the two.
350           var newNode       = document.createElement('li');
351           var isSecure;
352           if (document.location.hash != '') {
353             isSecure        = !this.nextUrl.match(/\?plain$/);
354           } else {
355             isSecure        =  this.nextUrl.match(/^https:/);
356           }
357           newNode.innerHTML = (isSecure ? '&#10004; ' : '') + 'Secure';
358           if (node.nextSibling) {
359             entries.insertBefore(newNode, node.nextSibling);
360           } else {
361             entries.appendChild(newNode);
362           }
363           actions[i++]      = this.toggleSSL;
364           node              = newNode;
365         }
366         node.id             = 'endconfig';
367       }
368     }
369   }
370
371 };
372
373 ShellInABox.prototype.about = function() {
374   alert("Shell In A Box version " + "2.10 (revision 239)" +
375         "\nCopyright 2008-2010 by Markus Gutschke\n" +
376         "For more information check http://shellinabox.com" +
377         (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
378          "\n\n" +
379          "This product includes software developed by the OpenSSL Project\n" +
380          "for use in the OpenSSL Toolkit. (http://www.openssl.org/)\n" +
381          "\n" +
382          "This product includes cryptographic software written by " +
383          "Eric Young\n(eay@cryptsoft.com)" :
384          ""));
385 };
386
387
388 // VT100.js -- JavaScript based terminal emulator
389 // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
390 //
391 // This program is free software; you can redistribute it and/or modify
392 // it under the terms of the GNU General Public License version 2 as
393 // published by the Free Software Foundation.
394 //
395 // This program is distributed in the hope that it will be useful,
396 // but WITHOUT ANY WARRANTY; without even the implied warranty of
397 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
398 // GNU General Public License for more details.
399 //
400 // You should have received a copy of the GNU General Public License along
401 // with this program; if not, write to the Free Software Foundation, Inc.,
402 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
403 //
404 // In addition to these license terms, the author grants the following
405 // additional rights:
406 //
407 // If you modify this program, or any covered work, by linking or
408 // combining it with the OpenSSL project's OpenSSL library (or a
409 // modified version of that library), containing parts covered by the
410 // terms of the OpenSSL or SSLeay licenses, the author
411 // grants you additional permission to convey the resulting work.
412 // Corresponding Source for a non-source form of such a combination
413 // shall include the source code for the parts of OpenSSL used as well
414 // as that of the covered work.
415 //
416 // You may at your option choose to remove this additional permission from
417 // the work, or from any part of it.
418 //
419 // It is possible to build this program in a way that it loads OpenSSL
420 // libraries at run-time. If doing so, the following notices are required
421 // by the OpenSSL and SSLeay licenses:
422 //
423 // This product includes software developed by the OpenSSL Project
424 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
425 //
426 // This product includes cryptographic software written by Eric Young
427 // (eay@cryptsoft.com)
428 //
429 //
430 // The most up-to-date version of this program is always available from
431 // http://shellinabox.com
432 //
433 //
434 // Notes:
435 //
436 // The author believes that for the purposes of this license, you meet the
437 // requirements for publishing the source code, if your web server publishes
438 // the source in unmodified form (i.e. with licensing information, comments,
439 // formatting, and identifier names intact). If there are technical reasons
440 // that require you to make changes to the source code when serving the
441 // JavaScript (e.g to remove pre-processor directives from the source), these
442 // changes should be done in a reversible fashion.
443 //
444 // The author does not consider websites that reference this script in
445 // unmodified form, and web servers that serve this script in unmodified form
446 // to be derived works. As such, they are believed to be outside of the
447 // scope of this license and not subject to the rights or restrictions of the
448 // GNU General Public License.
449 //
450 // If in doubt, consult a legal professional familiar with the laws that
451 // apply in your country.
452
453 // #define ESnormal        0
454 // #define ESesc           1
455 // #define ESsquare        2
456 // #define ESgetpars       3
457 // #define ESgotpars       4
458 // #define ESdeviceattr    5
459 // #define ESfunckey       6
460 // #define EShash          7
461 // #define ESsetG0         8
462 // #define ESsetG1         9
463 // #define ESsetG2        10
464 // #define ESsetG3        11
465 // #define ESbang         12
466 // #define ESpercent      13
467 // #define ESignore       14
468 // #define ESnonstd       15
469 // #define ESpalette      16
470 // #define EStitle        17
471 // #define ESss2          18
472 // #define ESss3          19
473
474 // #define ATTR_DEFAULT   0x00F0
475 // #define ATTR_REVERSE   0x0100
476 // #define ATTR_UNDERLINE 0x0200
477 // #define ATTR_DIM       0x0400
478 // #define ATTR_BRIGHT    0x0800
479 // #define ATTR_BLINK     0x1000
480
481 // #define MOUSE_DOWN     0
482 // #define MOUSE_UP       1
483 // #define MOUSE_CLICK    2
484
485 function VT100(container) {
486   if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
487     this.urlRE            = null;
488   } else {
489     this.urlRE            = new RegExp(
490     // Known URL protocol are "http", "https", and "ftp".
491     '(?:http|https|ftp)://' +
492
493     // Optionally allow username and passwords.
494     '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
495
496     // Hostname.
497     '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
498     '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
499     '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
500
501     // Port
502     '(?::[1-9][0-9]*)?' +
503
504     // Path.
505     '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
506
507     (linkifyURLs <= 1 ? '' :
508     // Also support URLs without a protocol (assume "http").
509     // Optional username and password.
510     '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
511
512     // Hostnames must end with a well-known top-level domain or must be
513     // numeric.
514     '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
515     'localhost|' +
516     '(?:(?!-)' +
517         '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
518     '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
519     'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
520     'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
521     'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
522     'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
523     'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
524     'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
525     'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
526     'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
527     'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
528     'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
529     'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
530     'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
531
532     // Port
533     '(?::[1-9][0-9]{0,4})?' +
534
535     // Path.
536     '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
537
538     // In addition, support e-mail address. Optionally, recognize "mailto:"
539     '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
540
541     // Username:
542     '[-_.+a-zA-Z0-9]+@' +
543
544     // Hostname.
545     '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
546     '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
547     'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
548     'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
549     'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
550     'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
551     'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
552     'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
553     'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
554     'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
555     'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
556     'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
557     'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
558     'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
559
560     // Optional arguments
561     '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
562   }
563   this.getUserSettings();
564   this.initializeElements(container);
565   this.maxScrollbackLines = 500;
566   this.npar               = 0;
567   this.par                = [ ];
568   this.isQuestionMark     = false;
569   this.savedX             = [ ];
570   this.savedY             = [ ];
571   this.savedAttr          = [ ];
572   this.savedUseGMap       = 0;
573   this.savedGMap          = [ this.Latin1Map, this.VT100GraphicsMap,
574                               this.CodePage437Map, this.DirectToFontMap ];
575   this.savedValid         = [ ];
576   this.respondString      = '';
577   this.titleString        = '';
578   this.internalClipboard  = undefined;
579   this.reset(true);
580 }
581
582 VT100.prototype.reset = function(clearHistory) {
583   this.isEsc                                         = 0 /* ESnormal */;
584   this.needWrap                                      = false;
585   this.autoWrapMode                                  = true;
586   this.dispCtrl                                      = false;
587   this.toggleMeta                                    = false;
588   this.insertMode                                    = false;
589   this.applKeyMode                                   = false;
590   this.cursorKeyMode                                 = false;
591   this.crLfMode                                      = false;
592   this.offsetMode                                    = false;
593   this.mouseReporting                                = false;
594   this.printing                                      = false;
595   if (typeof this.printWin != 'undefined' &&
596       this.printWin && !this.printWin.closed) {
597     this.printWin.close();
598   }
599   this.printWin                                      = null;
600   this.utfEnabled                                    = this.utfPreferred;
601   this.utfCount                                      = 0;
602   this.utfChar                                       = 0;
603   this.color                                         = 'ansi0 bgAnsi15';
604   this.style                                         = '';
605   this.attr                                          = 0x00F0 /* ATTR_DEFAULT */;
606   this.useGMap                                       = 0;
607   this.GMap                                          = [ this.Latin1Map,
608                                                          this.VT100GraphicsMap,
609                                                          this.CodePage437Map,
610                                                          this.DirectToFontMap];
611   this.translate                                     = this.GMap[this.useGMap];
612   this.top                                           = 0;
613   this.bottom                                        = this.terminalHeight;
614   this.lastCharacter                                 = ' ';
615   this.userTabStop                                   = [ ];
616
617   if (clearHistory) {
618     for (var i = 0; i < 2; i++) {
619       while (this.console[i].firstChild) {
620         this.console[i].removeChild(this.console[i].firstChild);
621       }
622     }
623   }
624
625   this.enableAlternateScreen(false);
626
627   var wasCompressed                                  = false;
628   var transform                                      = this.getTransformName();
629   if (transform) {
630     for (var i = 0; i < 2; ++i) {
631       wasCompressed                  |= this.console[i].style[transform] != '';
632       this.console[i].style[transform]               = '';
633     }
634     this.cursor.style[transform]                     = '';
635     this.space.style[transform]                      = '';
636     if (transform == 'filter') {
637       this.console[this.currentScreen].style.width   = '';
638     }
639   }
640   this.scale                                         = 1.0;
641   if (wasCompressed) {
642     this.resizer();
643   }
644
645   this.gotoXY(0, 0);
646   this.showCursor();
647   this.isInverted                                    = false;
648   this.refreshInvertedState();
649   this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
650                    this.color, this.style);
651 };
652
653 VT100.prototype.addListener = function(elem, event, listener) {
654   try {
655     if (elem.addEventListener) {
656       elem.addEventListener(event, listener, false);
657     } else {
658       elem.attachEvent('on' + event, listener);
659     }
660   } catch (e) {
661   }
662 };
663
664 VT100.prototype.getUserSettings = function() {
665   // Compute hash signature to identify the entries in the userCSS menu.
666   // If the menu is unchanged from last time, default values can be
667   // looked up in a cookie associated with this page.
668   this.signature            = 3;
669   this.utfPreferred         = true;
670   this.visualBell           = typeof suppressAllAudio != 'undefined' &&
671                               suppressAllAudio;
672   this.autoprint            = true;
673   this.softKeyboard         = false;
674   this.blinkingCursor       = true;
675   if (this.visualBell) {
676     this.signature          = Math.floor(16807*this.signature + 1) %
677                                          ((1 << 31) - 1);
678   }
679   if (typeof userCSSList != 'undefined') {
680     for (var i = 0; i < userCSSList.length; ++i) {
681       var label             = userCSSList[i][0];
682       for (var j = 0; j < label.length; ++j) {
683         this.signature      = Math.floor(16807*this.signature+
684                                          label.charCodeAt(j)) %
685                                          ((1 << 31) - 1);
686       }
687       if (userCSSList[i][1]) {
688         this.signature      = Math.floor(16807*this.signature + 1) %
689                                          ((1 << 31) - 1);
690       }
691     }
692   }
693
694   var key                   = 'shellInABox=' + this.signature + ':';
695   var settings              = document.cookie.indexOf(key);
696   if (settings >= 0) {
697     settings                = document.cookie.substr(settings + key.length).
698                                                    replace(/([0-1]*).*/, "$1");
699     if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
700                                 0 : userCSSList.length)) {
701       this.utfPreferred     = settings.charAt(0) != '0';
702       this.visualBell       = settings.charAt(1) != '0';
703       this.autoprint        = settings.charAt(2) != '0';
704       this.softKeyboard     = settings.charAt(3) != '0';
705       this.blinkingCursor   = settings.charAt(4) != '0';
706       if (typeof userCSSList != 'undefined') {
707         for (var i = 0; i < userCSSList.length; ++i) {
708           userCSSList[i][2] = settings.charAt(i + 5) != '0';
709         }
710       }
711     }
712   }
713   this.utfEnabled           = this.utfPreferred;
714 };
715
716 VT100.prototype.storeUserSettings = function() {
717   var settings  = 'shellInABox=' + this.signature + ':' +
718                   (this.utfEnabled     ? '1' : '0') +
719                   (this.visualBell     ? '1' : '0') +
720                   (this.autoprint      ? '1' : '0') +
721                   (this.softKeyboard   ? '1' : '0') +
722                   (this.blinkingCursor ? '1' : '0');
723   if (typeof userCSSList != 'undefined') {
724     for (var i = 0; i < userCSSList.length; ++i) {
725       settings += userCSSList[i][2] ? '1' : '0';
726     }
727   }
728   var d         = new Date();
729   d.setDate(d.getDate() + 3653);
730   document.cookie = settings + ';expires=' + d.toGMTString();
731 };
732
733 VT100.prototype.initializeUserCSSStyles = function() {
734   this.usercssActions                    = [];
735   if (typeof userCSSList != 'undefined') {
736     var menu                             = '';
737     var group                            = '';
738     var wasSingleSel                     = 1;
739     var beginOfGroup                     = 0;
740     for (var i = 0; i <= userCSSList.length; ++i) {
741       if (i < userCSSList.length) {
742         var label                        = userCSSList[i][0];
743         var newGroup                     = userCSSList[i][1];
744         var enabled                      = userCSSList[i][2];
745
746         // Add user style sheet to document
747         var style                        = document.createElement('link');
748         var id                           = document.createAttribute('id');
749         id.nodeValue                     = 'usercss-' + i;
750         style.setAttributeNode(id);
751         var rel                          = document.createAttribute('rel');
752         rel.nodeValue                    = 'stylesheet';
753         style.setAttributeNode(rel);
754         var href                         = document.createAttribute('href');
755         href.nodeValue                   = 'usercss-' + i + '.css';
756         style.setAttributeNode(href);
757         var type                         = document.createAttribute('type');
758         type.nodeValue                   = 'text/css';
759         style.setAttributeNode(type);
760         document.getElementsByTagName('head')[0].appendChild(style);
761         style.disabled                   = !enabled;
762       }
763
764       // Add entry to menu
765       if (newGroup || i == userCSSList.length) {
766         if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
767           // The last group had multiple entries that are mutually exclusive;
768           // or the previous to last group did. In either case, we need to
769           // append a "<hr />" before we can add the last group to the menu.
770           menu                          += '<hr />';
771         }
772         wasSingleSel                     = i - beginOfGroup < 1;
773         menu                            += group;
774         group                            = '';
775
776         for (var j = beginOfGroup; j < i; ++j) {
777           this.usercssActions[this.usercssActions.length] =
778             function(vt100, current, begin, count) {
779
780               // Deselect all other entries in the group, then either select
781               // (for multiple entries in group) or toggle (for on/off entry)
782               // the current entry.
783               return function() {
784                 var entry                = vt100.getChildById(vt100.menu,
785                                                               'beginusercss');
786                 var i                    = -1;
787                 var j                    = -1;
788                 for (var c = count; c > 0; ++j) {
789                   if (entry.tagName == 'LI') {
790                     if (++i >= begin) {
791                       --c;
792                       var label          = vt100.usercss.childNodes[j];
793
794                       // Restore label to just the text content
795                       if (typeof label.textContent == 'undefined') {
796                         var s            = label.innerText;
797                         label.innerHTML  = '';
798                         label.appendChild(document.createTextNode(s));
799                       } else {
800                         label.textContent= label.textContent;
801                       }
802
803                       // User style sheets are numbered sequentially
804                       var sheet          = document.getElementById(
805                                                                'usercss-' + i);
806                       if (i == current) {
807                         if (count == 1) {
808                           sheet.disabled = !sheet.disabled;
809                         } else {
810                           sheet.disabled = false;
811                         }
812                         if (!sheet.disabled) {
813                           label.innerHTML= '<img src="/webshell/enabled.gif" />' +
814                                            label.innerHTML;
815                         }
816                       } else {
817                         sheet.disabled   = true;
818                       }
819                       userCSSList[i][2]  = !sheet.disabled;
820                     }
821                   }
822                   entry                  = entry.nextSibling;
823                 }
824
825                 // If the font size changed, adjust cursor and line dimensions
826                 this.cursor.style.cssText= '';
827                 this.cursorWidth         = this.cursor.clientWidth;
828                 this.cursorHeight        = this.lineheight.clientHeight;
829                 for (i = 0; i < this.console.length; ++i) {
830                   for (var line = this.console[i].firstChild; line;
831                        line = line.nextSibling) {
832                     line.style.height    = this.cursorHeight + 'px';
833                   }
834                 }
835                 vt100.resizer();
836               };
837             }(this, j, beginOfGroup, i - beginOfGroup);
838         }
839
840         if (i == userCSSList.length) {
841           break;
842         }
843
844         beginOfGroup                     = i;
845       }
846       // Collect all entries in a group, before attaching them to the menu.
847       // This is necessary as we don't know whether this is a group of
848       // mutually exclusive options (which should be separated by "<hr />" on
849       // both ends), or whether this is a on/off toggle, which can be grouped
850       // together with other on/off options.
851       group                             +=
852         '<li>' + (enabled ? '<img src="/webshell/enabled.gif" />' : '') +
853                  label +
854         '</li>';
855     }
856     this.usercss.innerHTML               = menu;
857   }
858 };
859
860 VT100.prototype.resetLastSelectedKey = function(e) {
861   var key                          = this.lastSelectedKey;
862   if (!key) {
863     return false;
864   }
865
866   var position                     = this.mousePosition(e);
867
868   // We don't get all the necessary events to reliably reselect a key
869   // if we moved away from it and then back onto it. We approximate the
870   // behavior by remembering the key until either we release the mouse
871   // button (we might never get this event if the mouse has since left
872   // the window), or until we move away too far.
873   var box                          = this.keyboard.firstChild;
874   if (position[0] <  box.offsetLeft + key.offsetWidth ||
875       position[1] <  box.offsetTop + key.offsetHeight ||
876       position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
877       position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
878       position[0] <  box.offsetLeft + key.offsetLeft - key.offsetWidth ||
879       position[1] <  box.offsetTop + key.offsetTop - key.offsetHeight ||
880       position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
881       position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
882     if (this.lastSelectedKey.className) log.console('reset: deselecting');
883     this.lastSelectedKey.className = '';
884     this.lastSelectedKey           = undefined;
885   }
886   return false;
887 };
888
889 VT100.prototype.showShiftState = function(state) {
890   var style              = document.getElementById('shift_state');
891   if (state) {
892     this.setTextContentRaw(style,
893                            '#vt100 #keyboard .shifted {' +
894                              'display: inline }' +
895                            '#vt100 #keyboard .unshifted {' +
896                              'display: none }');
897   } else {
898     this.setTextContentRaw(style, '');
899   }
900   var elems              = this.keyboard.getElementsByTagName('I');
901   for (var i = 0; i < elems.length; ++i) {
902     if (elems[i].id == '16') {
903       elems[i].className = state ? 'selected' : '';
904     }
905   }
906 };
907
908 VT100.prototype.showCtrlState = function(state) {
909   var ctrl         = this.getChildById(this.keyboard, '17' /* Ctrl */);
910   if (ctrl) {
911     ctrl.className = state ? 'selected' : '';
912   }
913 };
914
915 VT100.prototype.showAltState = function(state) {
916   var alt         = this.getChildById(this.keyboard, '18' /* Alt */);
917   if (alt) {
918     alt.className = state ? 'selected' : '';
919   }
920 };
921
922 VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
923   var fake      = [ ];
924   fake.charCode = ch;
925   fake.keyCode  = key;
926   fake.ctrlKey  = ctrl;
927   fake.shiftKey = shift;
928   fake.altKey   = alt;
929   fake.metaKey  = alt;
930   return this.handleKey(fake);
931 };
932
933 VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
934   if (elem == undefined) {
935     return;
936   }
937   if (ch == '\u00A0') {
938     // &nbsp; should be treated as a regular space character.
939     ch                                  = ' ';
940   }
941   if (ch != undefined && CH == undefined) {
942     // For letter keys, we automatically compute the uppercase character code
943     // from the lowercase one.
944     CH                                  = ch.toUpperCase();
945   }
946   if (KEY == undefined && key != undefined) {
947     // Most keys have identically key codes for both lowercase and uppercase
948     // keypresses. Normally, only function keys would have distinct key codes,
949     // whereas regular keys have character codes.
950     KEY                                 = key;
951   } else if (KEY == undefined && CH != undefined) {
952     // For regular keys, copy the character code to the key code.
953     KEY                                 = CH.charCodeAt(0);
954   }
955   if (key == undefined && ch != undefined) {
956     // For regular keys, copy the character code to the key code.
957     key                                 = ch.charCodeAt(0);
958   }
959   // Convert characters to numeric character codes. If the character code
960   // is undefined (i.e. this is a function key), set it to zero.
961   ch                                    = ch ? ch.charCodeAt(0) : 0;
962   CH                                    = CH ? CH.charCodeAt(0) : 0;
963
964   // Mouse down events high light the key. We also set lastSelectedKey. This
965   // is needed to that mouseout/mouseover can keep track of the key that
966   // is currently being clicked.
967   this.addListener(elem, 'mousedown',
968     function(vt100, elem, key) { return function(e) {
969       if ((e.which || e.button) == 1) {
970         if (vt100.lastSelectedKey) {
971           vt100.lastSelectedKey.className= '';
972         }
973         // Highlight the key while the mouse button is held down.
974         if (key == 16 /* Shift */) {
975           if (!elem.className != vt100.isShift) {
976             vt100.showShiftState(!vt100.isShift);
977           }
978         } else if (key == 17 /* Ctrl */) {
979           if (!elem.className != vt100.isCtrl) {
980             vt100.showCtrlState(!vt100.isCtrl);
981           }
982         } else if (key == 18 /* Alt */) {
983           if (!elem.className != vt100.isAlt) {
984             vt100.showAltState(!vt100.isAlt);
985           }
986         } else {
987           elem.className                  = 'selected';
988         }
989         vt100.lastSelectedKey             = elem;
990       }
991       return false; }; }(this, elem, key));
992   var clicked                           =
993     // Modifier keys update the state of the keyboard, but do not generate
994     // any key clicks that get forwarded to the application.
995     key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
996     function(vt100, elem) { return function(e) {
997       if (elem == vt100.lastSelectedKey) {
998         if (key == 16 /* Shift */) {
999           // The user clicked the Shift key
1000           vt100.isShift                 = !vt100.isShift;
1001           vt100.showShiftState(vt100.isShift);
1002         } else if (key == 17 /* Ctrl */) {
1003           vt100.isCtrl                  = !vt100.isCtrl;
1004           vt100.showCtrlState(vt100.isCtrl);
1005         } else if (key == 18 /* Alt */) {
1006           vt100.isAlt                   = !vt100.isAlt;
1007           vt100.showAltState(vt100.isAlt);
1008         }
1009         vt100.lastSelectedKey           = undefined;
1010       }
1011       if (vt100.lastSelectedKey) {
1012         vt100.lastSelectedKey.className = '';
1013         vt100.lastSelectedKey           = undefined;
1014       }
1015       return false; }; }(this, elem) :
1016     // Regular keys generate key clicks, when the mouse button is released or
1017     // when a mouse click event is received.
1018     function(vt100, elem, ch, key, CH, KEY) { return function(e) {
1019       if (vt100.lastSelectedKey) {
1020         if (elem == vt100.lastSelectedKey) {
1021           // The user clicked a key.
1022           if (vt100.isShift) {
1023             vt100.clickedKeyboard(e, elem, CH, KEY,
1024                                   true, vt100.isCtrl, vt100.isAlt);
1025           } else {
1026             vt100.clickedKeyboard(e, elem, ch, key,
1027                                   false, vt100.isCtrl, vt100.isAlt);
1028           }
1029           vt100.isShift                 = false;
1030           vt100.showShiftState(false);
1031           vt100.isCtrl                  = false;
1032           vt100.showCtrlState(false);
1033           vt100.isAlt                   = false;
1034           vt100.showAltState(false);
1035         }
1036         vt100.lastSelectedKey.className = '';
1037         vt100.lastSelectedKey           = undefined;
1038       }
1039       elem.className                    = '';
1040       return false; }; }(this, elem, ch, key, CH, KEY);
1041   this.addListener(elem, 'mouseup', clicked);
1042   this.addListener(elem, 'click', clicked);
1043
1044   // When moving the mouse away from a key, check if any keys need to be
1045   // deselected.
1046   this.addListener(elem, 'mouseout',
1047     function(vt100, elem, key) { return function(e) {
1048       if (key == 16 /* Shift */) {
1049         if (!elem.className == vt100.isShift) {
1050           vt100.showShiftState(vt100.isShift);
1051         }
1052       } else if (key == 17 /* Ctrl */) {
1053         if (!elem.className == vt100.isCtrl) {
1054           vt100.showCtrlState(vt100.isCtrl);
1055         }
1056       } else if (key == 18 /* Alt */) {
1057         if (!elem.className == vt100.isAlt) {
1058           vt100.showAltState(vt100.isAlt);
1059         }
1060       } else if (elem.className) {
1061         elem.className                  = '';
1062         vt100.lastSelectedKey           = elem;
1063       } else if (vt100.lastSelectedKey) {
1064         vt100.resetLastSelectedKey(e);
1065       }
1066       return false; }; }(this, elem, key));
1067
1068   // When moving the mouse over a key, select it if the user is still holding
1069   // the mouse button down (i.e. elem == lastSelectedKey)
1070   this.addListener(elem, 'mouseover',
1071     function(vt100, elem, key) { return function(e) {
1072       if (elem == vt100.lastSelectedKey) {
1073         if (key == 16 /* Shift */) {
1074           if (!elem.className != vt100.isShift) {
1075             vt100.showShiftState(!vt100.isShift);
1076           }
1077         } else if (key == 17 /* Ctrl */) {
1078           if (!elem.className != vt100.isCtrl) {
1079             vt100.showCtrlState(!vt100.isCtrl);
1080           }
1081         } else if (key == 18 /* Alt */) {
1082           if (!elem.className != vt100.isAlt) {
1083             vt100.showAltState(!vt100.isAlt);
1084           }
1085         } else if (!elem.className) {
1086           elem.className                = 'selected';
1087         }
1088       } else {
1089         vt100.resetLastSelectedKey(e);
1090       }
1091       return false; }; }(this, elem, key));
1092 };
1093
1094 VT100.prototype.initializeKeyBindings = function(elem) {
1095   if (elem) {
1096     if (elem.nodeName == "I" || elem.nodeName == "B") {
1097       if (elem.id) {
1098         // Function keys. The Javascript keycode is part of the "id"
1099         var i     = parseInt(elem.id);
1100         if (i) {
1101           // If the id does not parse as a number, it is not a keycode.
1102           this.addKeyBinding(elem, undefined, i);
1103         }
1104       } else {
1105         var child = elem.firstChild;
1106         if (child) {
1107           if (child.nodeName == "#text") {
1108             // If the key only has a text node as a child, then it is a letter.
1109             // Automatically compute the lower and upper case version of the
1110             // key.
1111             var text = this.getTextContent(child) ||
1112                        this.getTextContent(elem);
1113             this.addKeyBinding(elem, text.toLowerCase());
1114           } else if (child.nextSibling) {
1115             // If the key has two children, they are the lower and upper case
1116             // character code, respectively.
1117             this.addKeyBinding(elem, this.getTextContent(child), undefined,
1118                                this.getTextContent(child.nextSibling));
1119           }
1120         }
1121       }
1122     }
1123   }
1124   // Recursively parse all other child nodes.
1125   for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
1126     this.initializeKeyBindings(elem);
1127   }
1128 };
1129
1130 VT100.prototype.initializeKeyboardButton = function() {
1131   // Configure mouse event handlers for button that displays/hides keyboard
1132   this.addListener(this.keyboardImage, 'click',
1133     function(vt100) { return function(e) {
1134       if (vt100.keyboard.style.display != '') {
1135         if (vt100.reconnectBtn.style.visibility != '') {
1136           vt100.initializeKeyboard();
1137           vt100.showSoftKeyboard();
1138         }
1139       } else {
1140         vt100.hideSoftKeyboard();
1141         vt100.input.focus();
1142       }
1143       return false; }; }(this));
1144
1145   // Enable button that displays keyboard
1146   if (this.softKeyboard) {
1147     this.keyboardImage.style.visibility = 'visible';
1148   }
1149 };
1150
1151 VT100.prototype.initializeKeyboard = function() {
1152   // Only need to initialize the keyboard the very first time. When doing so,
1153   // copy the keyboard layout from the iframe.
1154   if (this.keyboard.firstChild) {
1155     return;
1156   }
1157   this.keyboard.innerHTML               =
1158                                     this.layout.contentDocument.body.innerHTML;
1159   var box                               = this.keyboard.firstChild;
1160   this.hideSoftKeyboard();
1161
1162   // Configure mouse event handlers for on-screen keyboard
1163   this.addListener(this.keyboard, 'click',
1164     function(vt100) { return function(e) {
1165       vt100.hideSoftKeyboard();
1166       vt100.input.focus();
1167       return false; }; }(this));
1168   this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
1169   this.addListener(box, 'click', this.cancelEvent);
1170   this.addListener(box, 'mouseup',
1171     function(vt100) { return function(e) {
1172       if (vt100.lastSelectedKey) {
1173         vt100.lastSelectedKey.className = '';
1174         vt100.lastSelectedKey           = undefined;
1175       }
1176       return false; }; }(this));
1177   this.addListener(box, 'mouseout',
1178     function(vt100) { return function(e) {
1179       return vt100.resetLastSelectedKey(e); }; }(this));
1180   this.addListener(box, 'mouseover',
1181     function(vt100) { return function(e) {
1182       return vt100.resetLastSelectedKey(e); }; }(this));
1183
1184   // Configure SHIFT key behavior
1185   var style                             = document.createElement('style');
1186   var id                                = document.createAttribute('id');
1187   id.nodeValue                          = 'shift_state';
1188   style.setAttributeNode(id);
1189   var type                              = document.createAttribute('type');
1190   type.nodeValue                        = 'text/css';
1191   style.setAttributeNode(type);
1192   document.getElementsByTagName('head')[0].appendChild(style);
1193
1194   // Set up key bindings
1195   this.initializeKeyBindings(box);
1196 };
1197
1198 VT100.prototype.initializeElements = function(container) {
1199   // If the necessary objects have not already been defined in the HTML
1200   // page, create them now.
1201   if (container) {
1202     this.container             = container;
1203   } else if (!(this.container  = document.getElementById('vt100'))) {
1204     this.container             = document.createElement('div');
1205     this.container.id          = 'vt100';
1206     document.body.appendChild(this.container);
1207   }
1208
1209   if (!this.getChildById(this.container, 'reconnect')   ||
1210       !this.getChildById(this.container, 'menu')        ||
1211       !this.getChildById(this.container, 'keyboard')    ||
1212       !this.getChildById(this.container, 'kbd_button')  ||
1213       !this.getChildById(this.container, 'kbd_img')     ||
1214       !this.getChildById(this.container, 'layout')      ||
1215       !this.getChildById(this.container, 'scrollable')  ||
1216       !this.getChildById(this.container, 'console')     ||
1217       !this.getChildById(this.container, 'alt_console') ||
1218       !this.getChildById(this.container, 'ieprobe')     ||
1219       !this.getChildById(this.container, 'padding')     ||
1220       !this.getChildById(this.container, 'cursor')      ||
1221       !this.getChildById(this.container, 'lineheight')  ||
1222       !this.getChildById(this.container, 'usercss')     ||
1223       !this.getChildById(this.container, 'space')       ||
1224       !this.getChildById(this.container, 'input')       ||
1225       !this.getChildById(this.container, 'cliphelper')) {
1226     // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
1227     // we might get a pointless warning that a suitable plugin is not yet
1228     // installed. If in doubt, we'd rather just stay silent.
1229     var embed                  = '';
1230     try {
1231       if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
1232           'undefined') {
1233         embed                  = typeof suppressAllAudio != 'undefined' &&
1234                                  suppressAllAudio ? "" :
1235         '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
1236                        'id="beep_embed" ' +
1237                        'src="beep.wav" ' +
1238                        'autostart="false" ' +
1239                        'volume="100" ' +
1240                        'enablejavascript="true" ' +
1241                        'type="audio/x-wav" ' +
1242                        'height="16" ' +
1243                        'width="200" ' +
1244                        'style="position:absolute;left:-1000px;top:-1000px" />';
1245       }
1246     } catch (e) {
1247     }
1248
1249     this.container.innerHTML   =
1250                        '<div id="reconnect" style="visibility: hidden">' +
1251                          '<input type="button" value="Connect" ' +
1252                                 'onsubmit="return false" />' +
1253                        '</div>' +
1254                        '<div id="cursize" style="visibility: hidden">' +
1255                        '</div>' +
1256                        '<div id="menu"></div>' +
1257                        '<div id="keyboard" unselectable="on">' +
1258                        '</div>' +
1259                        '<div id="scrollable">' +
1260                          '<table id="kbd_button">' +
1261                            '<tr><td width="100%">&nbsp;</td>' +
1262                            '<td><img id="kbd_img" src="/webshell/keyboard.png" /></td>' +
1263                            '<td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr>' +
1264                          '</table>' +
1265                          '<pre id="lineheight">&nbsp;</pre>' +
1266                          '<pre id="console">' +
1267                            '<pre></pre>' +
1268                            '<div id="ieprobe"><span>&nbsp;</span></div>' +
1269                          '</pre>' +
1270                          '<pre id="alt_console" style="display: none"></pre>' +
1271                          '<div id="padding"></div>' +
1272                          '<pre id="cursor">&nbsp;</pre>' +
1273                        '</div>' +
1274                        '<div class="hidden">' +
1275                          '<div id="usercss"></div>' +
1276                          '<pre><div><span id="space"></span></div></pre>' +
1277                          '<input type="textfield" id="input" autocorrect="off" autocapitalize="off" />' +
1278                          '<input type="textfield" id="cliphelper" />' +
1279                          (typeof suppressAllAudio != 'undefined' &&
1280                           suppressAllAudio ? "" :
1281                          embed + '<bgsound id="beep_bgsound" loop=1 />') +
1282                           '<iframe id="layout" src="/webshell/keyboard.html" />' +
1283                         '</div>';
1284   }
1285
1286   // Find the object used for playing the "beep" sound, if any.
1287   if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
1288     this.beeper                = undefined;
1289   } else {
1290     this.beeper                = this.getChildById(this.container,
1291                                                    'beep_embed');
1292     if (!this.beeper || !this.beeper.Play) {
1293       this.beeper              = this.getChildById(this.container,
1294                                                    'beep_bgsound');
1295       if (!this.beeper || typeof this.beeper.src == 'undefined') {
1296         this.beeper            = undefined;
1297       }
1298     }
1299   }
1300
1301   // Initialize the variables for finding the text console and the
1302   // cursor.
1303   this.reconnectBtn            = this.getChildById(this.container,'reconnect');
1304   this.curSizeBox              = this.getChildById(this.container, 'cursize');
1305   this.menu                    = this.getChildById(this.container, 'menu');
1306   this.keyboard                = this.getChildById(this.container, 'keyboard');
1307   this.keyboardImage           = this.getChildById(this.container, 'kbd_img');
1308   this.layout                  = this.getChildById(this.container, 'layout');
1309   this.scrollable              = this.getChildById(this.container,
1310                                                                  'scrollable');
1311   this.lineheight              = this.getChildById(this.container,
1312                                                                  'lineheight');
1313   this.console                 =
1314                           [ this.getChildById(this.container, 'console'),
1315                             this.getChildById(this.container, 'alt_console') ];
1316   var ieProbe                  = this.getChildById(this.container, 'ieprobe');
1317   this.padding                 = this.getChildById(this.container, 'padding');
1318   this.cursor                  = this.getChildById(this.container, 'cursor');
1319   this.usercss                 = this.getChildById(this.container, 'usercss');
1320   this.space                   = this.getChildById(this.container, 'space');
1321   this.input                   = this.getChildById(this.container, 'input');
1322   this.cliphelper              = this.getChildById(this.container,
1323                                                                  'cliphelper');
1324
1325   // Add any user selectable style sheets to the menu
1326   this.initializeUserCSSStyles();
1327
1328   // Remember the dimensions of a standard character glyph. We would
1329   // expect that we could just check cursor.clientWidth/Height at any time,
1330   // but it turns out that browsers sometimes invalidate these values
1331   // (e.g. while displaying a print preview screen).
1332   this.cursorWidth             = this.cursor.clientWidth;
1333   this.cursorHeight            = this.lineheight.clientHeight;
1334
1335   // IE has a slightly different boxing model, that we need to compensate for
1336   this.isIE                    = ieProbe.offsetTop > 1;
1337   ieProbe                      = undefined;
1338   this.console.innerHTML       = '';
1339
1340   // Determine if the terminal window is positioned at the beginning of the
1341   // page, or if it is embedded somewhere else in the page. For full-screen
1342   // terminals, automatically resize whenever the browser window changes.
1343   var marginTop                = parseInt(this.getCurrentComputedStyle(
1344                                           document.body, 'marginTop'));
1345   var marginLeft               = parseInt(this.getCurrentComputedStyle(
1346                                           document.body, 'marginLeft'));
1347   var marginRight              = parseInt(this.getCurrentComputedStyle(
1348                                           document.body, 'marginRight'));
1349   var x                        = this.container.offsetLeft;
1350   var y                        = this.container.offsetTop;
1351   for (var parent = this.container; parent = parent.offsetParent; ) {
1352     x                         += parent.offsetLeft;
1353     y                         += parent.offsetTop;
1354   }
1355   this.isEmbedded              = marginTop != y ||
1356                                  marginLeft != x ||
1357                                  (window.innerWidth ||
1358                                   document.documentElement.clientWidth ||
1359                                   document.body.clientWidth) -
1360                                  marginRight != x + this.container.offsetWidth;
1361   if (!this.isEmbedded) {
1362     // Some browsers generate resize events when the terminal is first
1363     // shown. Disable showing the size indicator until a little bit after
1364     // the terminal has been rendered the first time.
1365     this.indicateSize          = false;
1366     setTimeout(function(vt100) {
1367       return function() {
1368         vt100.indicateSize     = true;
1369       };
1370     }(this), 100);
1371     this.addListener(window, 'resize',
1372                      function(vt100) {
1373                        return function() {
1374                          vt100.hideContextMenu();
1375                          vt100.resizer();
1376                          vt100.showCurrentSize();
1377                         }
1378                       }(this));
1379
1380     // Hide extra scrollbars attached to window
1381     document.body.style.margin = '0px';
1382     try { document.body.style.overflow ='hidden'; } catch (e) { }
1383     try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1384   }
1385
1386   // Set up onscreen soft keyboard
1387   this.initializeKeyboardButton();
1388
1389   // Hide context menu
1390   this.hideContextMenu();
1391
1392   // Add listener to reconnect button
1393   this.addListener(this.reconnectBtn.firstChild, 'click',
1394                    function(vt100) {
1395                      return function() {
1396                        var rc = vt100.reconnect();
1397                        vt100.input.focus();
1398                        return rc;
1399                      }
1400                    }(this));
1401
1402   // Add input listeners
1403   this.addListener(this.input, 'blur',
1404                    function(vt100) {
1405                      return function() { vt100.blurCursor(); } }(this));
1406   this.addListener(this.input, 'focus',
1407                    function(vt100) {
1408                      return function() { vt100.focusCursor(); } }(this));
1409   this.addListener(this.input, 'keydown',
1410                    function(vt100) {
1411                      return function(e) {
1412                        if (!e) e = window.event;
1413                        return vt100.keyDown(e); } }(this));
1414   this.addListener(this.input, 'keypress',
1415                    function(vt100) {
1416                      return function(e) {
1417                        if (!e) e = window.event;
1418                        return vt100.keyPressed(e); } }(this));
1419   this.addListener(this.input, 'keyup',
1420                    function(vt100) {
1421                      return function(e) {
1422                        if (!e) e = window.event;
1423                        return vt100.keyUp(e); } }(this));
1424
1425   // Attach listeners that move the focus to the <input> field. This way we
1426   // can make sure that we can receive keyboard input.
1427   var mouseEvent               = function(vt100, type) {
1428     return function(e) {
1429       if (!e) e = window.event;
1430       return vt100.mouseEvent(e, type);
1431     };
1432   };
1433   this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1434   this.addListener(this.scrollable,'mouseup',  mouseEvent(this, 1 /* MOUSE_UP */));
1435   this.addListener(this.scrollable,'click',    mouseEvent(this, 2 /* MOUSE_CLICK */));
1436
1437   // Check that browser supports drag and drop
1438   if ('draggable' in document.createElement('span')) {
1439       var dropEvent            = function (vt100) {
1440           return function(e) {
1441               if (!e) e = window.event;
1442               if (e.preventDefault) e.preventDefault();
1443               vt100.keysPressed(e.dataTransfer.getData('Text'));
1444               return false;
1445           };
1446       };
1447       // Tell the browser that we *can* drop on this target
1448       this.addListener(this.scrollable, 'dragover', cancel);
1449       this.addListener(this.scrollable, 'dragenter', cancel);
1450
1451       // Add a listener for the drop event
1452       this.addListener(this.scrollable, 'drop', dropEvent(this));
1453   }
1454
1455   // Initialize the blank terminal window.
1456   this.currentScreen           = 0;
1457   this.cursorX                 = 0;
1458   this.cursorY                 = 0;
1459   this.numScrollbackLines      = 0;
1460   this.top                     = 0;
1461   this.bottom                  = 0x7FFFFFFF;
1462   this.scale                   = 1.0;
1463   this.resizer();
1464   this.focusCursor();
1465   this.input.focus();
1466 };
1467
1468 function cancel(event) {
1469   if (event.preventDefault) {
1470     event.preventDefault();
1471   }
1472   return false;
1473 }
1474
1475 VT100.prototype.getChildById = function(parent, id) {
1476   var nodeList = parent.all || parent.getElementsByTagName('*');
1477   if (typeof nodeList.namedItem == 'undefined') {
1478     for (var i = 0; i < nodeList.length; i++) {
1479       if (nodeList[i].id == id) {
1480         return nodeList[i];
1481       }
1482     }
1483     return null;
1484   } else {
1485     var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1486     return elem ? elem[0] || elem : null;
1487   }
1488 };
1489
1490 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1491   if (typeof elem.currentStyle != 'undefined') {
1492     return elem.currentStyle[style];
1493   } else {
1494     return document.defaultView.getComputedStyle(elem, null)[style];
1495   }
1496 };
1497
1498 VT100.prototype.reconnect = function() {
1499   return false;
1500 };
1501
1502 VT100.prototype.showReconnect = function(state) {
1503   if (state) {
1504     this.hideSoftKeyboard();
1505     this.reconnectBtn.style.visibility = '';
1506   } else {
1507     this.reconnectBtn.style.visibility = 'hidden';
1508   }
1509 };
1510
1511 VT100.prototype.repairElements = function(console) {
1512   for (var line = console.firstChild; line; line = line.nextSibling) {
1513     if (!line.clientHeight) {
1514       var newLine = document.createElement(line.tagName);
1515       newLine.style.cssText       = line.style.cssText;
1516       newLine.className           = line.className;
1517       if (line.tagName == 'DIV') {
1518         for (var span = line.firstChild; span; span = span.nextSibling) {
1519           var newSpan             = document.createElement(span.tagName);
1520           newSpan.style.cssText   = span.style.cssText;
1521           newSpan.className       = span.className;
1522           this.setTextContent(newSpan, this.getTextContent(span));
1523           newLine.appendChild(newSpan);
1524         }
1525       } else {
1526         this.setTextContent(newLine, this.getTextContent(line));
1527       }
1528       line.parentNode.replaceChild(newLine, line);
1529       line                        = newLine;
1530     }
1531   }
1532 };
1533
1534 VT100.prototype.resized = function(w, h) {
1535 };
1536
1537 VT100.prototype.resizer = function() {
1538   // Hide onscreen soft keyboard
1539   this.hideSoftKeyboard();
1540
1541   // The cursor can get corrupted if the print-preview is displayed in Firefox.
1542   // Recreating it, will repair it.
1543   var newCursor                = document.createElement('pre');
1544   this.setTextContent(newCursor, ' ');
1545   newCursor.id                 = 'cursor';
1546   newCursor.style.cssText      = this.cursor.style.cssText;
1547   this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1548   if (!newCursor.clientHeight) {
1549     // Things are broken right now. This is probably because we are
1550     // displaying the print-preview. Just don't change any of our settings
1551     // until the print dialog is closed again.
1552     newCursor.parentNode.removeChild(newCursor);
1553     return;
1554   } else {
1555     // Swap the old broken cursor for the newly created one.
1556     this.cursor.parentNode.removeChild(this.cursor);
1557     this.cursor                = newCursor;
1558   }
1559
1560   // Really horrible things happen if the contents of the terminal changes
1561   // while the print-preview is showing. We get HTML elements that show up
1562   // in the DOM, but that do not take up any space. Find these elements and
1563   // try to fix them.
1564   this.repairElements(this.console[0]);
1565   this.repairElements(this.console[1]);
1566
1567   // Lock the cursor size to the size of a normal character. This helps with
1568   // characters that are taller/shorter than normal. Unfortunately, we will
1569   // still get confused if somebody enters a character that is wider/narrower
1570   // than normal. This can happen if the browser tries to substitute a
1571   // characters from a different font.
1572   this.cursor.style.width      = this.cursorWidth  + 'px';
1573   this.cursor.style.height     = this.cursorHeight + 'px';
1574
1575   // Adjust height for one pixel padding of the #vt100 element.
1576   // The latter is necessary to properly display the inactive cursor.
1577   var console                  = this.console[this.currentScreen];
1578   var height                   = (this.isEmbedded ? this.container.clientHeight
1579                                   : (window.innerHeight ||
1580                                      document.documentElement.clientHeight ||
1581                                      document.body.clientHeight))-1;
1582   var partial                  = height % this.cursorHeight;
1583   this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1584   this.padding.style.height    = (partial > 0 ? partial : 0) + 'px';
1585   var oldTerminalHeight        = this.terminalHeight;
1586   this.updateWidth();
1587   this.updateHeight();
1588
1589   // Clip the cursor to the visible screen.
1590   var cx                       = this.cursorX;
1591   var cy                       = this.cursorY + this.numScrollbackLines;
1592
1593   // The alternate screen never keeps a scroll back buffer.
1594   this.updateNumScrollbackLines();
1595   while (this.currentScreen && this.numScrollbackLines > 0) {
1596     console.removeChild(console.firstChild);
1597     this.numScrollbackLines--;
1598   }
1599   cy                          -= this.numScrollbackLines;
1600   if (cx < 0) {
1601     cx                         = 0;
1602   } else if (cx > this.terminalWidth) {
1603     cx                         = this.terminalWidth - 1;
1604     if (cx < 0) {
1605       cx                       = 0;
1606     }
1607   }
1608   if (cy < 0) {
1609     cy                         = 0;
1610   } else if (cy > this.terminalHeight) {
1611     cy                         = this.terminalHeight - 1;
1612     if (cy < 0) {
1613       cy                       = 0;
1614     }
1615   }
1616
1617   // Clip the scroll region to the visible screen.
1618   if (this.bottom > this.terminalHeight ||
1619       this.bottom == oldTerminalHeight) {
1620     this.bottom                = this.terminalHeight;
1621   }
1622   if (this.top >= this.bottom) {
1623     this.top                   = this.bottom-1;
1624     if (this.top < 0) {
1625       this.top                 = 0;
1626     }
1627   }
1628
1629   // Truncate lines, if necessary. Explicitly reposition cursor (this is
1630   // particularly important after changing the screen number), and reset
1631   // the scroll region to the default.
1632   this.truncateLines(this.terminalWidth);
1633   this.putString(cx, cy, '', undefined);
1634   this.scrollable.scrollTop    = this.numScrollbackLines *
1635                                  this.cursorHeight + 1;
1636
1637   // Update classNames for lines in the scrollback buffer
1638   var line                     = console.firstChild;
1639   for (var i = 0; i < this.numScrollbackLines; i++) {
1640     line.className             = 'scrollback';
1641     line                       = line.nextSibling;
1642   }
1643   while (line) {
1644     line.className             = '';
1645     line                       = line.nextSibling;
1646   }
1647
1648   // Reposition the reconnect button
1649   this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1650                                   this.scale -
1651                                   this.reconnectBtn.clientWidth)/2 + 'px';
1652   this.reconnectBtn.style.top  = (this.terminalHeight*this.cursorHeight-
1653                                   this.reconnectBtn.clientHeight)/2 + 'px';
1654
1655   // Send notification that the window size has been changed
1656   this.resized(this.terminalWidth, this.terminalHeight);
1657 };
1658
1659 VT100.prototype.showCurrentSize = function() {
1660   if (!this.indicateSize) {
1661     return;
1662   }
1663   this.curSizeBox.innerHTML             = '' + this.terminalWidth + 'x' +
1664                                                this.terminalHeight;
1665   this.curSizeBox.style.left            =
1666                                       (this.terminalWidth*this.cursorWidth/
1667                                        this.scale -
1668                                        this.curSizeBox.clientWidth)/2 + 'px';
1669   this.curSizeBox.style.top             =
1670                                       (this.terminalHeight*this.cursorHeight -
1671                                        this.curSizeBox.clientHeight)/2 + 'px';
1672   this.curSizeBox.style.visibility      = '';
1673   if (this.curSizeTimeout) {
1674     clearTimeout(this.curSizeTimeout);
1675   }
1676
1677   // Only show the terminal size for a short amount of time after resizing.
1678   // Then hide this information, again. Some browsers generate resize events
1679   // throughout the entire resize operation. This is nice, and we will show
1680   // the terminal size while the user is dragging the window borders.
1681   // Other browsers only generate a single event when the user releases the
1682   // mouse. In those cases, we can only show the terminal size once at the
1683   // end of the resize operation.
1684   this.curSizeTimeout                   = setTimeout(function(vt100) {
1685     return function() {
1686       vt100.curSizeTimeout              = null;
1687       vt100.curSizeBox.style.visibility = 'hidden';
1688     };
1689   }(this), 1000);
1690 };
1691
1692 VT100.prototype.selection = function() {
1693   try {
1694     return '' + (window.getSelection && window.getSelection() ||
1695                  document.selection && document.selection.type == 'Text' &&
1696                  document.selection.createRange().text || '');
1697   } catch (e) {
1698   }
1699   return '';
1700 };
1701
1702 VT100.prototype.cancelEvent = function(event) {
1703   try {
1704     // For non-IE browsers
1705     event.stopPropagation();
1706     event.preventDefault();
1707   } catch (e) {
1708   }
1709   try {
1710     // For IE
1711     event.cancelBubble = true;
1712     event.returnValue  = false;
1713     event.button       = 0;
1714     event.keyCode      = 0;
1715   } catch (e) {
1716   }
1717   return false;
1718 };
1719
1720 VT100.prototype.mousePosition = function(event) {
1721   var offsetX      = this.container.offsetLeft;
1722   var offsetY      = this.container.offsetTop;
1723   for (var e = this.container; e = e.offsetParent; ) {
1724     offsetX       += e.offsetLeft;
1725     offsetY       += e.offsetTop;
1726   }
1727   return [ event.clientX - offsetX,
1728            event.clientY - offsetY ];
1729 };
1730
1731 VT100.prototype.mouseEvent = function(event, type) {
1732   // If any text is currently selected, do not move the focus as that would
1733   // invalidate the selection.
1734   var selection    = this.selection();
1735   if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1736     this.input.focus();
1737   }
1738
1739   // Compute mouse position in characters.
1740   var position     = this.mousePosition(event);
1741   var x            = Math.floor(position[0] / this.cursorWidth);
1742   var y            = Math.floor((position[1] + this.scrollable.scrollTop) /
1743                                 this.cursorHeight) - this.numScrollbackLines;
1744   var inside       = true;
1745   if (x >= this.terminalWidth) {
1746     x              = this.terminalWidth - 1;
1747     inside         = false;
1748   }
1749   if (x < 0) {
1750     x              = 0;
1751     inside         = false;
1752   }
1753   if (y >= this.terminalHeight) {
1754     y              = this.terminalHeight - 1;
1755     inside         = false;
1756   }
1757   if (y < 0) {
1758     y              = 0;
1759     inside         = false;
1760   }
1761
1762   // Compute button number and modifier keys.
1763   var button       = type != 0 /* MOUSE_DOWN */ ? 3 :
1764                      typeof event.pageX != 'undefined' ? event.button :
1765                      [ undefined, 0, 2, 0, 1, 0, 1, 0  ][event.button];
1766   if (button != undefined) {
1767     if (event.shiftKey) {
1768       button      |= 0x04;
1769     }
1770     if (event.altKey || event.metaKey) {
1771       button      |= 0x08;
1772     }
1773     if (event.ctrlKey) {
1774       button      |= 0x10;
1775     }
1776   }
1777
1778   // Report mouse events if they happen inside of the current screen and
1779   // with the SHIFT key unpressed. Both of these restrictions do not apply
1780   // for button releases, as we always want to report those.
1781   if (this.mouseReporting && !selection.length &&
1782       (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1783     if (inside || type != 0 /* MOUSE_DOWN */) {
1784       if (button != undefined) {
1785         var report = '\u001B[M' + String.fromCharCode(button + 32) +
1786                                   String.fromCharCode(x      + 33) +
1787                                   String.fromCharCode(y      + 33);
1788         if (type != 2 /* MOUSE_CLICK */) {
1789           this.keysPressed(report);
1790         }
1791
1792         // If we reported the event, stop propagating it (not sure, if this
1793         // actually works on most browsers; blocking the global "oncontextmenu"
1794         // even is still necessary).
1795         return this.cancelEvent(event);
1796       }
1797     }
1798   }
1799
1800   // Bring up context menu.
1801   if (button == 2 && !event.shiftKey) {
1802     if (type == 0 /* MOUSE_DOWN */) {
1803       this.showContextMenu(position[0], position[1]);
1804     }
1805     return this.cancelEvent(event);
1806   }
1807
1808   if (this.mouseReporting) {
1809     try {
1810       event.shiftKey         = false;
1811     } catch (e) {
1812     }
1813   }
1814
1815   return true;
1816 };
1817
1818 VT100.prototype.replaceChar = function(s, ch, repl) {
1819   for (var i = -1;;) {
1820     i = s.indexOf(ch, i + 1);
1821     if (i < 0) {
1822       break;
1823     }
1824     s = s.substr(0, i) + repl + s.substr(i + 1);
1825   }
1826   return s;
1827 };
1828
1829 VT100.prototype.htmlEscape = function(s) {
1830   return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1831                 s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;'), ' ', '\u00A0');
1832 };
1833
1834 VT100.prototype.getTextContent = function(elem) {
1835   return elem.textContent ||
1836          (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1837 };
1838
1839 VT100.prototype.setTextContentRaw = function(elem, s) {
1840   // Updating the content of an element is an expensive operation. It actually
1841   // pays off to first check whether the element is still unchanged.
1842   if (typeof elem.textContent == 'undefined') {
1843     if (elem.innerText != s) {
1844       try {
1845         elem.innerText = s;
1846       } catch (e) {
1847         // Very old versions of IE do not allow setting innerText. Instead,
1848         // remove all children, by setting innerHTML and then set the text
1849         // using DOM methods.
1850         elem.innerHTML = '';
1851         elem.appendChild(document.createTextNode(
1852                                           this.replaceChar(s, ' ', '\u00A0')));
1853       }
1854     }
1855   } else {
1856     if (elem.textContent != s) {
1857       elem.textContent = s;
1858     }
1859   }
1860 };
1861
1862 VT100.prototype.setTextContent = function(elem, s) {
1863   // Check if we find any URLs in the text. If so, automatically convert them
1864   // to links.
1865   if (this.urlRE && this.urlRE.test(s)) {
1866     var inner          = '';
1867     for (;;) {
1868       var consumed = 0;
1869       if (RegExp.leftContext != null) {
1870         inner         += this.htmlEscape(RegExp.leftContext);
1871         consumed      += RegExp.leftContext.length;
1872       }
1873       var url          = this.htmlEscape(RegExp.lastMatch);
1874       var fullUrl      = url;
1875
1876       // If no protocol was specified, try to guess a reasonable one.
1877       if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1878           url.indexOf('ftp://')  < 0 && url.indexOf('mailto:')  < 0) {
1879         var slash      = url.indexOf('/');
1880         var at         = url.indexOf('@');
1881         var question   = url.indexOf('?');
1882         if (at > 0 &&
1883             (at < question || question < 0) &&
1884             (slash < 0 || (question > 0 && slash > question))) {
1885           fullUrl      = 'mailto:' + url;
1886         } else {
1887           fullUrl      = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1888                           url;
1889         }
1890       }
1891
1892       inner           += '<a target="vt100Link" href="' + fullUrl +
1893                          '">' + url + '</a>';
1894       consumed        += RegExp.lastMatch.length;
1895       s                = s.substr(consumed);
1896       if (!this.urlRE.test(s)) {
1897         if (RegExp.rightContext != null) {
1898           inner       += this.htmlEscape(RegExp.rightContext);
1899         }
1900         break;
1901       }
1902     }
1903     elem.innerHTML     = inner;
1904     return;
1905   }
1906
1907   this.setTextContentRaw(elem, s);
1908 };
1909
1910 VT100.prototype.insertBlankLine = function(y, color, style) {
1911   // Insert a blank line a position y. This method ignores the scrollback
1912   // buffer. The caller has to add the length of the scrollback buffer to
1913   // the position, if necessary.
1914   // If the position is larger than the number of current lines, this
1915   // method just adds a new line right after the last existing one. It does
1916   // not add any missing lines in between. It is the caller's responsibility
1917   // to do so.
1918   if (!color) {
1919     color                = 'ansi0 bgAnsi15';
1920   }
1921   if (!style) {
1922     style                = '';
1923   }
1924   var line;
1925   if (color != 'ansi0 bgAnsi15' && !style) {
1926     line                 = document.createElement('pre');
1927     this.setTextContent(line, '\n');
1928   } else {
1929     line                 = document.createElement('div');
1930     var span             = document.createElement('span');
1931     span.style.cssText   = style;
1932     span.className       = color;
1933     this.setTextContent(span, this.spaces(this.terminalWidth));
1934     line.appendChild(span);
1935   }
1936   line.style.height      = this.cursorHeight + 'px';
1937   var console            = this.console[this.currentScreen];
1938   if (console.childNodes.length > y) {
1939     console.insertBefore(line, console.childNodes[y]);
1940   } else {
1941     console.appendChild(line);
1942   }
1943 };
1944
1945 VT100.prototype.updateWidth = function() {
1946   this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1947                                   this.cursorWidth*this.scale);
1948   return this.terminalWidth;
1949 };
1950
1951 VT100.prototype.updateHeight = function() {
1952   // We want to be able to display either a terminal window that fills the
1953   // entire browser window, or a terminal window that is contained in a
1954   // <div> which is embededded somewhere in the web page.
1955   if (this.isEmbedded) {
1956     // Embedded terminal. Use size of the containing <div> (id="vt100").
1957     this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1958                                      this.cursorHeight);
1959   } else {
1960     // Use the full browser window.
1961     this.terminalHeight = Math.floor(((window.innerHeight ||
1962                                        document.documentElement.clientHeight ||
1963                                        document.body.clientHeight)-1)/
1964                                      this.cursorHeight);
1965   }
1966   return this.terminalHeight;
1967 };
1968
1969 VT100.prototype.updateNumScrollbackLines = function() {
1970   var scrollback          = Math.floor(
1971                                 this.console[this.currentScreen].offsetHeight /
1972                                 this.cursorHeight) -
1973                             this.terminalHeight;
1974   this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1975   return this.numScrollbackLines;
1976 };
1977
1978 VT100.prototype.truncateLines = function(width) {
1979   if (width < 0) {
1980     width             = 0;
1981   }
1982   for (var line = this.console[this.currentScreen].firstChild; line;
1983        line = line.nextSibling) {
1984     if (line.tagName == 'DIV') {
1985       var x           = 0;
1986
1987       // Traverse current line and truncate it once we saw "width" characters
1988       for (var span = line.firstChild; span;
1989            span = span.nextSibling) {
1990         var s         = this.getTextContent(span);
1991         var l         = s.length;
1992         if (x + l > width) {
1993           this.setTextContent(span, s.substr(0, width - x));
1994           while (span.nextSibling) {
1995             line.removeChild(line.lastChild);
1996           }
1997           break;
1998         }
1999         x            += l;
2000       }
2001       // Prune white space from the end of the current line
2002       var span       = line.lastChild;
2003       while (span &&
2004              span.className == 'ansi0 bgAnsi15' &&
2005              !span.style.cssText.length) {
2006         // Scan backwards looking for first non-space character
2007         var s         = this.getTextContent(span);
2008         for (var i = s.length; i--; ) {
2009           if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2010             if (i+1 != s.length) {
2011               this.setTextContent(s.substr(0, i+1));
2012             }
2013             span      = null;
2014             break;
2015           }
2016         }
2017         if (span) {
2018           var sibling = span;
2019           span        = span.previousSibling;
2020           if (span) {
2021             // Remove blank <span>'s from end of line
2022             line.removeChild(sibling);
2023           } else {
2024             // Remove entire line (i.e. <div>), if empty
2025             var blank = document.createElement('pre');
2026             blank.style.height = this.cursorHeight + 'px';
2027             this.setTextContent(blank, '\n');
2028             line.parentNode.replaceChild(blank, line);
2029           }
2030         }
2031       }
2032     }
2033   }
2034 };
2035
2036 VT100.prototype.putString = function(x, y, text, color, style) {
2037   if (!color) {
2038     color                           = 'ansi0 bgAnsi15';
2039   }
2040   if (!style) {
2041     style                           = '';
2042   }
2043   var yIdx                          = y + this.numScrollbackLines;
2044   var line;
2045   var sibling;
2046   var s;
2047   var span;
2048   var xPos                          = 0;
2049   var console                       = this.console[this.currentScreen];
2050   if (!text.length && (yIdx >= console.childNodes.length ||
2051                        console.childNodes[yIdx].tagName != 'DIV')) {
2052     // Positioning cursor to a blank location
2053     span                            = null;
2054   } else {
2055     // Create missing blank lines at end of page
2056     while (console.childNodes.length <= yIdx) {
2057       // In order to simplify lookups, we want to make sure that each line
2058       // is represented by exactly one element (and possibly a whole bunch of
2059       // children).
2060       // For non-blank lines, we can create a <div> containing one or more
2061       // <span>s. For blank lines, this fails as browsers tend to optimize them
2062       // away. But fortunately, a <pre> tag containing a newline character
2063       // appears to work for all browsers (a &nbsp; would also work, but then
2064       // copying from the browser window would insert superfluous spaces into
2065       // the clipboard).
2066       this.insertBlankLine(yIdx);
2067     }
2068     line                            = console.childNodes[yIdx];
2069
2070     // If necessary, promote blank '\n' line to a <div> tag
2071     if (line.tagName != 'DIV') {
2072       var div                       = document.createElement('div');
2073       div.style.height              = this.cursorHeight + 'px';
2074       div.innerHTML                 = '<span></span>';
2075       console.replaceChild(div, line);
2076       line                          = div;
2077     }
2078
2079     // Scan through list of <span>'s until we find the one where our text
2080     // starts
2081     span                            = line.firstChild;
2082     var len;
2083     while (span.nextSibling && xPos < x) {
2084       len                           = this.getTextContent(span).length;
2085       if (xPos + len > x) {
2086         break;
2087       }
2088       xPos                         += len;
2089       span                          = span.nextSibling;
2090     }
2091
2092     if (text.length) {
2093       // If current <span> is not long enough, pad with spaces or add new
2094       // span
2095       s                             = this.getTextContent(span);
2096       var oldColor                  = span.className;
2097       var oldStyle                  = span.style.cssText;
2098       if (xPos + s.length < x) {
2099         if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
2100           span                      = document.createElement('span');
2101           line.appendChild(span);
2102           span.className            = 'ansi0 bgAnsi15';
2103           span.style.cssText        = '';
2104           oldColor                  = 'ansi0 bgAnsi15';
2105           oldStyle                  = '';
2106           xPos                     += s.length;
2107           s                         = '';
2108         }
2109         do {
2110           s                        += ' ';
2111         } while (xPos + s.length < x);
2112       }
2113
2114       // If styles do not match, create a new <span>
2115       var del                       = text.length - s.length + x - xPos;
2116       if (oldColor != color ||
2117           (oldStyle != style && (oldStyle || style))) {
2118         if (xPos == x) {
2119           // Replacing text at beginning of existing <span>
2120           if (text.length >= s.length) {
2121             // New text is equal or longer than existing text
2122             s                       = text;
2123           } else {
2124             // Insert new <span> before the current one, then remove leading
2125             // part of existing <span>, adjust style of new <span>, and finally
2126             // set its contents
2127             sibling                 = document.createElement('span');
2128             line.insertBefore(sibling, span);
2129             this.setTextContent(span, s.substr(text.length));
2130             span                    = sibling;
2131             s                       = text;
2132           }
2133         } else {
2134           // Replacing text some way into the existing <span>
2135           var remainder             = s.substr(x + text.length - xPos);
2136           this.setTextContent(span, s.substr(0, x - xPos));
2137           xPos                      = x;
2138           sibling                   = document.createElement('span');
2139           if (span.nextSibling) {
2140             line.insertBefore(sibling, span.nextSibling);
2141             span                    = sibling;
2142             if (remainder.length) {
2143               sibling               = document.createElement('span');
2144               sibling.className     = oldColor;
2145               sibling.style.cssText = oldStyle;
2146               this.setTextContent(sibling, remainder);
2147               line.insertBefore(sibling, span.nextSibling);
2148             }
2149           } else {
2150             line.appendChild(sibling);
2151             span                    = sibling;
2152             if (remainder.length) {
2153               sibling               = document.createElement('span');
2154               sibling.className     = oldColor;
2155               sibling.style.cssText = oldStyle;
2156               this.setTextContent(sibling, remainder);
2157               line.appendChild(sibling);
2158             }
2159           }
2160           s                         = text;
2161         }
2162         span.className              = color;
2163         span.style.cssText          = style;
2164       } else {
2165         // Overwrite (partial) <span> with new text
2166         s                           = s.substr(0, x - xPos) +
2167           text +
2168           s.substr(x + text.length - xPos);
2169       }
2170       this.setTextContent(span, s);
2171
2172
2173       // Delete all subsequent <span>'s that have just been overwritten
2174       sibling                       = span.nextSibling;
2175       while (del > 0 && sibling) {
2176         s                           = this.getTextContent(sibling);
2177         len                         = s.length;
2178         if (len <= del) {
2179           line.removeChild(sibling);
2180           del                      -= len;
2181           sibling                   = span.nextSibling;
2182         } else {
2183           this.setTextContent(sibling, s.substr(del));
2184           break;
2185         }
2186       }
2187
2188       // Merge <span> with next sibling, if styles are identical
2189       if (sibling && span.className == sibling.className &&
2190           span.style.cssText == sibling.style.cssText) {
2191         this.setTextContent(span,
2192                             this.getTextContent(span) +
2193                             this.getTextContent(sibling));
2194         line.removeChild(sibling);
2195       }
2196     }
2197   }
2198
2199   // Position cursor
2200   this.cursorX                      = x + text.length;
2201   if (this.cursorX >= this.terminalWidth) {
2202     this.cursorX                    = this.terminalWidth - 1;
2203     if (this.cursorX < 0) {
2204       this.cursorX                  = 0;
2205     }
2206   }
2207   var pixelX                        = -1;
2208   var pixelY                        = -1;
2209   if (!this.cursor.style.visibility) {
2210     var idx                         = this.cursorX - xPos;
2211     if (span) {
2212       // If we are in a non-empty line, take the cursor Y position from the
2213       // other elements in this line. If dealing with broken, non-proportional
2214       // fonts, this is likely to yield better results.
2215       pixelY                        = span.offsetTop +
2216                                       span.offsetParent.offsetTop;
2217       s                             = this.getTextContent(span);
2218       var nxtIdx                    = idx - s.length;
2219       if (nxtIdx < 0) {
2220         this.setTextContent(this.cursor, s.charAt(idx));
2221         pixelX                      = span.offsetLeft +
2222                                       idx*span.offsetWidth / s.length;
2223       } else {
2224         if (nxtIdx == 0) {
2225           pixelX                    = span.offsetLeft + span.offsetWidth;
2226         }
2227         if (span.nextSibling) {
2228           s                         = this.getTextContent(span.nextSibling);
2229           this.setTextContent(this.cursor, s.charAt(nxtIdx));
2230           if (pixelX < 0) {
2231             pixelX                  = span.nextSibling.offsetLeft +
2232                                       nxtIdx*span.offsetWidth / s.length;
2233           }
2234         } else {
2235           this.setTextContent(this.cursor, ' ');
2236         }
2237       }
2238     } else {
2239       this.setTextContent(this.cursor, ' ');
2240     }
2241   }
2242   if (pixelX >= 0) {
2243     this.cursor.style.left          = (pixelX + (this.isIE ? 1 : 0))/
2244                                       this.scale + 'px';
2245   } else {
2246     this.setTextContent(this.space, this.spaces(this.cursorX));
2247     this.cursor.style.left          = (this.space.offsetWidth +
2248                                        console.offsetLeft)/this.scale + 'px';
2249   }
2250   this.cursorY                      = yIdx - this.numScrollbackLines;
2251   if (pixelY >= 0) {
2252     this.cursor.style.top           = pixelY + 'px';
2253   } else {
2254     this.cursor.style.top           = yIdx*this.cursorHeight +
2255                                       console.offsetTop + 'px';
2256   }
2257
2258   if (text.length) {
2259     // Merge <span> with previous sibling, if styles are identical
2260     if ((sibling = span.previousSibling) &&
2261         span.className == sibling.className &&
2262         span.style.cssText == sibling.style.cssText) {
2263       this.setTextContent(span,
2264                           this.getTextContent(sibling) +
2265                           this.getTextContent(span));
2266       line.removeChild(sibling);
2267     }
2268
2269     // Prune white space from the end of the current line
2270     span                            = line.lastChild;
2271     while (span &&
2272            span.className == 'ansi0 bgAnsi15' &&
2273            !span.style.cssText.length) {
2274       // Scan backwards looking for first non-space character
2275       s                             = this.getTextContent(span);
2276       for (var i = s.length; i--; ) {
2277         if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
2278           if (i+1 != s.length) {
2279             this.setTextContent(s.substr(0, i+1));
2280           }
2281           span                      = null;
2282           break;
2283         }
2284       }
2285       if (span) {
2286         sibling                     = span;
2287         span                        = span.previousSibling;
2288         if (span) {
2289           // Remove blank <span>'s from end of line
2290           line.removeChild(sibling);
2291         } else {
2292           // Remove entire line (i.e. <div>), if empty
2293           var blank                 = document.createElement('pre');
2294           blank.style.height        = this.cursorHeight + 'px';
2295           this.setTextContent(blank, '\n');
2296           line.parentNode.replaceChild(blank, line);
2297         }
2298       }
2299     }
2300   }
2301 };
2302
2303 VT100.prototype.gotoXY = function(x, y) {
2304   if (x >= this.terminalWidth) {
2305     x           = this.terminalWidth - 1;
2306   }
2307   if (x < 0) {
2308     x           = 0;
2309   }
2310   var minY, maxY;
2311   if (this.offsetMode) {
2312     minY        = this.top;
2313     maxY        = this.bottom;
2314   } else {
2315     minY        = 0;
2316     maxY        = this.terminalHeight;
2317   }
2318   if (y >= maxY) {
2319     y           = maxY - 1;
2320   }
2321   if (y < minY) {
2322     y           = minY;
2323   }
2324   this.putString(x, y, '', undefined);
2325   this.needWrap = false;
2326 };
2327
2328 VT100.prototype.gotoXaY = function(x, y) {
2329   this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
2330 };
2331
2332 VT100.prototype.refreshInvertedState = function() {
2333   if (this.isInverted) {
2334     this.scrollable.className += ' inverted';
2335   } else {
2336     this.scrollable.className = this.scrollable.className.
2337                                                      replace(/ *inverted/, '');
2338   }
2339 };
2340
2341 VT100.prototype.enableAlternateScreen = function(state) {
2342   // Don't do anything, if we are already on the desired screen
2343   if ((state ? 1 : 0) == this.currentScreen) {
2344     // Calling the resizer is not actually necessary. But it is a good way
2345     // of resetting state that might have gotten corrupted.
2346     this.resizer();
2347     return;
2348   }
2349
2350   // We save the full state of the normal screen, when we switch away from it.
2351   // But for the alternate screen, no saving is necessary. We always reset
2352   // it when we switch to it.
2353   if (state) {
2354     this.saveCursor();
2355   }
2356
2357   // Display new screen, and initialize state (the resizer does that for us).
2358   this.currentScreen                                 = state ? 1 : 0;
2359   this.console[1-this.currentScreen].style.display   = 'none';
2360   this.console[this.currentScreen].style.display     = '';
2361
2362   // Select appropriate character pitch.
2363   var transform                                      = this.getTransformName();
2364   if (transform) {
2365     if (state) {
2366       // Upon enabling the alternate screen, we switch to 80 column mode. But
2367       // upon returning to the regular screen, we restore the mode that was
2368       // in effect previously.
2369       this.console[1].style[transform]               = '';
2370     }
2371     var style                                        =
2372                              this.console[this.currentScreen].style[transform];
2373     this.cursor.style[transform]                     = style;
2374     this.space.style[transform]                      = style;
2375     this.scale                                       = style == '' ? 1.0:1.65;
2376     if (transform == 'filter') {
2377        this.console[this.currentScreen].style.width  = style == '' ? '165%':'';
2378     }
2379   }
2380   this.resizer();
2381
2382   // If we switched to the alternate screen, reset it completely. Otherwise,
2383   // restore the saved state.
2384   if (state) {
2385     this.gotoXY(0, 0);
2386     this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2387   } else {
2388     this.restoreCursor();
2389   }
2390 };
2391
2392 VT100.prototype.hideCursor = function() {
2393   var hidden = this.cursor.style.visibility == 'hidden';
2394   if (!hidden) {
2395     this.cursor.style.visibility = 'hidden';
2396     return true;
2397   }
2398   return false;
2399 };
2400
2401 VT100.prototype.showCursor = function(x, y) {
2402   if (this.cursor.style.visibility) {
2403     this.cursor.style.visibility = '';
2404     this.putString(x == undefined ? this.cursorX : x,
2405                    y == undefined ? this.cursorY : y,
2406                    '', undefined);
2407     return true;
2408   }
2409   return false;
2410 };
2411
2412 VT100.prototype.scrollBack = function() {
2413   var i                     = this.scrollable.scrollTop -
2414                               this.scrollable.clientHeight;
2415   this.scrollable.scrollTop = i < 0 ? 0 : i;
2416 };
2417
2418 VT100.prototype.scrollFore = function() {
2419   var i                     = this.scrollable.scrollTop +
2420                               this.scrollable.clientHeight;
2421   this.scrollable.scrollTop = i > this.numScrollbackLines *
2422                                   this.cursorHeight + 1
2423                               ? this.numScrollbackLines *
2424                                 this.cursorHeight + 1
2425                               : i;
2426 };
2427
2428 VT100.prototype.spaces = function(i) {
2429   var s = '';
2430   while (i-- > 0) {
2431     s += ' ';
2432   }
2433   return s;
2434 };
2435
2436 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2437   w         += x;
2438   if (x < 0) {
2439     x        = 0;
2440   }
2441   if (w > this.terminalWidth) {
2442     w        = this.terminalWidth;
2443   }
2444   if ((w    -= x) <= 0) {
2445     return;
2446   }
2447   h         += y;
2448   if (y < 0) {
2449     y        = 0;
2450   }
2451   if (h > this.terminalHeight) {
2452     h        = this.terminalHeight;
2453   }
2454   if ((h    -= y) <= 0) {
2455     return;
2456   }
2457
2458   // Special case the situation where we clear the entire screen, and we do
2459   // not have a scrollback buffer. In that case, we should just remove all
2460   // child nodes.
2461   if (!this.numScrollbackLines &&
2462       w == this.terminalWidth && h == this.terminalHeight &&
2463       (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2464     var console = this.console[this.currentScreen];
2465     while (console.lastChild) {
2466       console.removeChild(console.lastChild);
2467     }
2468     this.putString(this.cursorX, this.cursorY, '', undefined);
2469   } else {
2470     var hidden = this.hideCursor();
2471     var cx     = this.cursorX;
2472     var cy     = this.cursorY;
2473     var s      = this.spaces(w);
2474     for (var i = y+h; i-- > y; ) {
2475       this.putString(x, i, s, color, style);
2476     }
2477     hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2478   }
2479 };
2480
2481 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2482   var text                            = [ ];
2483   var className                       = [ ];
2484   var style                           = [ ];
2485   var console                         = this.console[this.currentScreen];
2486   if (sY >= console.childNodes.length) {
2487     text[0]                           = this.spaces(w);
2488     className[0]                      = undefined;
2489     style[0]                          = undefined;
2490   } else {
2491     var line = console.childNodes[sY];
2492     if (line.tagName != 'DIV' || !line.childNodes.length) {
2493       text[0]                         = this.spaces(w);
2494       className[0]                    = undefined;
2495       style[0]                        = undefined;
2496     } else {
2497       var x                           = 0;
2498       for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2499         var s                         = this.getTextContent(span);
2500         var len                       = s.length;
2501         if (x + len > sX) {
2502           var o                       = sX > x ? sX - x : 0;
2503           text[text.length]           = s.substr(o, w);
2504           className[className.length] = span.className;
2505           style[style.length]         = span.style.cssText;
2506           w                          -= len - o;
2507         }
2508         x                            += len;
2509       }
2510       if (w > 0) {
2511         text[text.length]             = this.spaces(w);
2512         className[className.length]   = undefined;
2513         style[style.length]           = undefined;
2514       }
2515     }
2516   }
2517   var hidden                          = this.hideCursor();
2518   var cx                              = this.cursorX;
2519   var cy                              = this.cursorY;
2520   for (var i = 0; i < text.length; i++) {
2521     var color;
2522     if (className[i]) {
2523       color                           = className[i];
2524     } else {
2525       color                           = 'ansi0 bgAnsi15';
2526     }
2527     this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2528     dX                               += text[i].length;
2529   }
2530   hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2531 };
2532
2533 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2534                                         color, style) {
2535   var left             = incX < 0 ? -incX : 0;
2536   var right            = incX > 0 ?  incX : 0;
2537   var up               = incY < 0 ? -incY : 0;
2538   var down             = incY > 0 ?  incY : 0;
2539
2540   // Clip region against terminal size
2541   var dontScroll       = null;
2542   w                   += x;
2543   if (x < left) {
2544     x                  = left;
2545   }
2546   if (w > this.terminalWidth - right) {
2547     w                  = this.terminalWidth - right;
2548   }
2549   if ((w              -= x) <= 0) {
2550     dontScroll         = 1;
2551   }
2552   h                   += y;
2553   if (y < up) {
2554     y                  = up;
2555   }
2556   if (h > this.terminalHeight - down) {
2557     h                  = this.terminalHeight - down;
2558   }
2559   if ((h              -= y) < 0) {
2560     dontScroll         = 1;
2561   }
2562   if (!dontScroll) {
2563     if (style && style.indexOf('underline')) {
2564       // Different terminal emulators disagree on the attributes that
2565       // are used for scrolling. The consensus seems to be, never to
2566       // fill with underlined spaces. N.B. this is different from the
2567       // cases when the user blanks a region. User-initiated blanking
2568       // always fills with all of the current attributes.
2569       style            = style.replace(/text-decoration:underline;/, '');
2570     }
2571
2572     // Compute current scroll position
2573     var scrollPos      = this.numScrollbackLines -
2574                       (this.scrollable.scrollTop-1) / this.cursorHeight;
2575
2576     // Determine original cursor position. Hide cursor temporarily to avoid
2577     // visual artifacts.
2578     var hidden         = this.hideCursor();
2579     var cx             = this.cursorX;
2580     var cy             = this.cursorY;
2581     var console        = this.console[this.currentScreen];
2582
2583     if (!incX && !x && w == this.terminalWidth) {
2584       // Scrolling entire lines
2585       if (incY < 0) {
2586         // Scrolling up
2587         if (!this.currentScreen && y == -incY &&
2588             h == this.terminalHeight + incY) {
2589           // Scrolling up with adding to the scrollback buffer. This is only
2590           // possible if there are at least as many lines in the console,
2591           // as the terminal is high
2592           while (console.childNodes.length < this.terminalHeight) {
2593             this.insertBlankLine(this.terminalHeight);
2594           }
2595
2596           // Add new lines at bottom in order to force scrolling
2597           for (var i = 0; i < y; i++) {
2598             this.insertBlankLine(console.childNodes.length, color, style);
2599           }
2600
2601           // Adjust the number of lines in the scrollback buffer by
2602           // removing excess entries.
2603           this.updateNumScrollbackLines();
2604           while (this.numScrollbackLines >
2605                  (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2606             console.removeChild(console.firstChild);
2607             this.numScrollbackLines--;
2608           }
2609
2610           // Mark lines in the scrollback buffer, so that they do not get
2611           // printed.
2612           for (var i = this.numScrollbackLines, j = -incY;
2613                i-- > 0 && j-- > 0; ) {
2614             console.childNodes[i].className = 'scrollback';
2615           }
2616         } else {
2617           // Scrolling up without adding to the scrollback buffer.
2618           for (var i = -incY;
2619                i-- > 0 &&
2620                console.childNodes.length >
2621                this.numScrollbackLines + y + incY; ) {
2622             console.removeChild(console.childNodes[
2623                                           this.numScrollbackLines + y + incY]);
2624           }
2625
2626           // If we used to have a scrollback buffer, then we must make sure
2627           // that we add back blank lines at the bottom of the terminal.
2628           // Similarly, if we are scrolling in the middle of the screen,
2629           // we must add blank lines to ensure that the bottom of the screen
2630           // does not move up.
2631           if (this.numScrollbackLines > 0 ||
2632               console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2633             for (var i = -incY; i-- > 0; ) {
2634               this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2635                                    color, style);
2636             }
2637           }
2638         }
2639       } else {
2640         // Scrolling down
2641         for (var i = incY;
2642              i-- > 0 &&
2643              console.childNodes.length > this.numScrollbackLines + y + h; ) {
2644           console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2645         }
2646         for (var i = incY; i--; ) {
2647           this.insertBlankLine(this.numScrollbackLines + y, color, style);
2648         }
2649       }
2650     } else {
2651       // Scrolling partial lines
2652       if (incY <= 0) {
2653         // Scrolling up or horizontally within a line
2654         for (var i = y + this.numScrollbackLines;
2655              i < y + this.numScrollbackLines + h;
2656              i++) {
2657           this.copyLineSegment(x + incX, i + incY, x, i, w);
2658         }
2659       } else {
2660         // Scrolling down
2661         for (var i = y + this.numScrollbackLines + h;
2662              i-- > y + this.numScrollbackLines; ) {
2663           this.copyLineSegment(x + incX, i + incY, x, i, w);
2664         }
2665       }
2666
2667       // Clear blank regions
2668       if (incX > 0) {
2669         this.clearRegion(x, y, incX, h, color, style);
2670       } else if (incX < 0) {
2671         this.clearRegion(x + w + incX, y, -incX, h, color, style);
2672       }
2673       if (incY > 0) {
2674         this.clearRegion(x, y, w, incY, color, style);
2675       } else if (incY < 0) {
2676         this.clearRegion(x, y + h + incY, w, -incY, color, style);
2677       }
2678     }
2679
2680     // Reset scroll position
2681     this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2682                                 this.cursorHeight + 1;
2683
2684     // Move cursor back to its original position
2685     hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2686   }
2687 };
2688
2689 VT100.prototype.copy = function(selection) {
2690   if (selection == undefined) {
2691     selection                = this.selection();
2692   }
2693   this.internalClipboard     = undefined;
2694   if (selection.length) {
2695     try {
2696       // IE
2697       this.cliphelper.value  = selection;
2698       this.cliphelper.select();
2699       this.cliphelper.createTextRange().execCommand('copy');
2700     } catch (e) {
2701       this.internalClipboard = selection;
2702     }
2703     this.cliphelper.value    = '';
2704   }
2705 };
2706
2707 VT100.prototype.copyLast = function() {
2708   // Opening the context menu can remove the selection. We try to prevent this
2709   // from happening, but that is not possible for all browsers. So, instead,
2710   // we compute the selection before showing the menu.
2711   this.copy(this.lastSelection);
2712 };
2713
2714 VT100.prototype.pasteFnc = function() {
2715   var clipboard     = undefined;
2716   if (this.internalClipboard != undefined) {
2717     clipboard       = this.internalClipboard;
2718   } else {
2719     try {
2720       this.cliphelper.value = '';
2721       this.cliphelper.createTextRange().execCommand('paste');
2722       clipboard     = this.cliphelper.value;
2723     } catch (e) {
2724     }
2725   }
2726   this.cliphelper.value = '';
2727   if (clipboard && this.menu.style.visibility == 'hidden') {
2728     return function() {
2729       this.keysPressed('' + clipboard);
2730     };
2731   } else {
2732     return undefined;
2733   }
2734 };
2735
2736 VT100.prototype.pasteBrowserFnc = function() {
2737   var clipboard     = prompt("Paste into this box:","");
2738   if (clipboard != undefined) {
2739      return this.keysPressed('' + clipboard);
2740   }
2741 };
2742
2743 VT100.prototype.toggleUTF = function() {
2744   this.utfEnabled   = !this.utfEnabled;
2745
2746   // We always persist the last value that the user selected. Not necessarily
2747   // the last value that a random program requested.
2748   this.utfPreferred = this.utfEnabled;
2749 };
2750
2751 VT100.prototype.toggleBell = function() {
2752   this.visualBell = !this.visualBell;
2753 };
2754
2755 VT100.prototype.toggleSoftKeyboard = function() {
2756   this.softKeyboard = !this.softKeyboard;
2757   this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2758 };
2759
2760 VT100.prototype.deselectKeys = function(elem) {
2761   if (elem && elem.className == 'selected') {
2762     elem.className = '';
2763   }
2764   for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2765     this.deselectKeys(elem);
2766   }
2767 };
2768
2769 VT100.prototype.showSoftKeyboard = function() {
2770   // Make sure no key is currently selected
2771   this.lastSelectedKey           = undefined;
2772   this.deselectKeys(this.keyboard);
2773   this.isShift                   = false;
2774   this.showShiftState(false);
2775   this.isCtrl                    = false;
2776   this.showCtrlState(false);
2777   this.isAlt                     = false;
2778   this.showAltState(false);
2779
2780   this.keyboard.style.left       = '0px';
2781   this.keyboard.style.top        = '0px';
2782   this.keyboard.style.width      = this.container.offsetWidth  + 'px';
2783   this.keyboard.style.height     = this.container.offsetHeight + 'px';
2784   this.keyboard.style.visibility = 'hidden';
2785   this.keyboard.style.display    = '';
2786
2787   var kbd                        = this.keyboard.firstChild;
2788   var scale                      = 1.0;
2789   var transform                  = this.getTransformName();
2790   if (transform) {
2791     kbd.style[transform]         = '';
2792     if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2793       scale                      = (kbd.offsetWidth/
2794                                     this.container.offsetWidth)/0.9;
2795     }
2796     if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2797       scale                      = Math.max((kbd.offsetHeight/
2798                                              this.container.offsetHeight)/0.9);
2799     }
2800     var style                    = this.getTransformStyle(transform,
2801                                               scale > 1.0 ? scale : undefined);
2802     kbd.style[transform]         = style;
2803   }
2804   if (transform == 'filter') {
2805     scale                        = 1.0;
2806   }
2807   kbd.style.left                 = ((this.container.offsetWidth -
2808                                      kbd.offsetWidth/scale)/2) + 'px';
2809   kbd.style.top                  = ((this.container.offsetHeight -
2810                                      kbd.offsetHeight/scale)/2) + 'px';
2811
2812   this.keyboard.style.visibility = 'visible';
2813 };
2814
2815 VT100.prototype.hideSoftKeyboard = function() {
2816   this.keyboard.style.display    = 'none';
2817 };
2818
2819 VT100.prototype.toggleCursorBlinking = function() {
2820   this.blinkingCursor = !this.blinkingCursor;
2821 };
2822
2823 VT100.prototype.about = function() {
2824   alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2825         "\nCopyright 2008-2010 by Markus Gutschke\n" +
2826         "For more information check http://shellinabox.com");
2827 };
2828
2829 VT100.prototype.hideContextMenu = function() {
2830   this.menu.style.visibility = 'hidden';
2831   this.menu.style.top        = '-100px';
2832   this.menu.style.left       = '-100px';
2833   this.menu.style.width      = '0px';
2834   this.menu.style.height     = '0px';
2835 };
2836
2837 VT100.prototype.extendContextMenu = function(entries, actions) {
2838 };
2839
2840 VT100.prototype.showContextMenu = function(x, y) {
2841   this.menu.innerHTML         =
2842     '<table class="popup" ' +
2843            'cellpadding="0" cellspacing="0">' +
2844       '<tr><td>' +
2845         '<ul id="menuentries">' +
2846           '<li id="beginclipboard">Copy</li>' +
2847           '<li id="endclipboard">Paste</li>' +
2848           '<li id="browserclipboard">Paste from browser</li>' +
2849           '<hr />' +
2850           '<li id="reset">Reset</li>' +
2851           '<hr />' +
2852           '<li id="beginconfig">' +
2853              (this.utfEnabled ? '<img src="/webshell/enabled.gif" />' : '') +
2854              'Unicode</li>' +
2855           '<li>' +
2856              (this.visualBell ? '<img src="/webshell/enabled.gif" />' : '') +
2857              'Visual Bell</li>'+
2858           '<li>' +
2859              (this.softKeyboard ? '<img src="/webshell/enabled.gif" />' : '') +
2860              'Onscreen Keyboard</li>' +
2861           '<li id="endconfig">' +
2862              (this.blinkingCursor ? '<img src="/webshell/enabled.gif" />' : '') +
2863              'Blinking Cursor</li>'+
2864           (this.usercss.firstChild ?
2865            '<hr id="beginusercss" />' +
2866            this.usercss.innerHTML +
2867            '<hr id="endusercss" />' :
2868            '<hr />') +
2869           '<li id="about">About...</li>' +
2870         '</ul>' +
2871       '</td></tr>' +
2872     '</table>';
2873
2874   var popup                   = this.menu.firstChild;
2875   var menuentries             = this.getChildById(popup, 'menuentries');
2876
2877   // Determine menu entries that should be disabled
2878   this.lastSelection          = this.selection();
2879   if (!this.lastSelection.length) {
2880     menuentries.firstChild.className
2881                               = 'disabled';
2882   }
2883   var p                       = this.pasteFnc();
2884   if (!p) {
2885     menuentries.childNodes[1].className
2886                               = 'disabled';
2887   }
2888
2889   // Actions for default items
2890   var actions                 = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2891                                   this.toggleUTF, this.toggleBell,
2892                                   this.toggleSoftKeyboard,
2893                                   this.toggleCursorBlinking ];
2894
2895   // Actions for user CSS styles (if any)
2896   for (var i = 0; i < this.usercssActions.length; ++i) {
2897     actions[actions.length]   = this.usercssActions[i];
2898   }
2899   actions[actions.length]     = this.about;
2900
2901   // Allow subclasses to dynamically add entries to the context menu
2902   this.extendContextMenu(menuentries, actions);
2903
2904   // Hook up event listeners
2905   for (var node = menuentries.firstChild, i = 0; node;
2906        node = node.nextSibling) {
2907     if (node.tagName == 'LI') {
2908       if (node.className != 'disabled') {
2909         this.addListener(node, 'mouseover',
2910                          function(vt100, node) {
2911                            return function() {
2912                              node.className = 'hover';
2913                            }
2914                          }(this, node));
2915         this.addListener(node, 'mouseout',
2916                          function(vt100, node) {
2917                            return function() {
2918                              node.className = '';
2919                            }
2920                          }(this, node));
2921         this.addListener(node, 'mousedown',
2922                          function(vt100, action) {
2923                            return function(event) {
2924                              vt100.hideContextMenu();
2925                              action.call(vt100);
2926                              vt100.storeUserSettings();
2927                              return vt100.cancelEvent(event || window.event);
2928                            }
2929                          }(this, actions[i]));
2930         this.addListener(node, 'mouseup',
2931                          function(vt100) {
2932                            return function(event) {
2933                              return vt100.cancelEvent(event || window.event);
2934                            }
2935                          }(this));
2936         this.addListener(node, 'mouseclick',
2937                          function(vt100) {
2938                            return function(event) {
2939                              return vt100.cancelEvent(event || window.event);
2940                            }
2941                          }());
2942       }
2943       i++;
2944     }
2945   }
2946
2947   // Position menu next to the mouse pointer
2948   this.menu.style.left        = '0px';
2949   this.menu.style.top         = '0px';
2950   this.menu.style.width       =  this.container.offsetWidth  + 'px';
2951   this.menu.style.height      =  this.container.offsetHeight + 'px';
2952   popup.style.left            = '0px';
2953   popup.style.top             = '0px';
2954
2955   var margin                  = 2;
2956   if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2957     x              = this.container.offsetWidth-popup.clientWidth - margin - 1;
2958   }
2959   if (x < margin) {
2960     x                         = margin;
2961   }
2962   if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2963     y            = this.container.offsetHeight-popup.clientHeight - margin - 1;
2964   }
2965   if (y < margin) {
2966     y                         = margin;
2967   }
2968   popup.style.left            = x + 'px';
2969   popup.style.top             = y + 'px';
2970
2971   // Block all other interactions with the terminal emulator
2972   this.addListener(this.menu, 'click', function(vt100) {
2973                                          return function() {
2974                                            vt100.hideContextMenu();
2975                                          }
2976                                        }(this));
2977
2978   // Show the menu
2979   this.menu.style.visibility  = '';
2980 };
2981
2982 VT100.prototype.keysPressed = function(ch) {
2983   for (var i = 0; i < ch.length; i++) {
2984     var c = ch.charCodeAt(i);
2985     this.vt100(c >= 7 && c <= 15 ||
2986                c == 24 || c == 26 || c == 27 || c >= 32
2987                ? String.fromCharCode(c) : '<' + c + '>');
2988   }
2989 };
2990
2991 VT100.prototype.applyModifiers = function(ch, event) {
2992   if (ch) {
2993     if (event.ctrlKey) {
2994       if (ch >= 32 && ch <= 127) {
2995         // For historic reasons, some control characters are treated specially
2996         switch (ch) {
2997         case /* 3 */ 51: ch  =  27; break;
2998         case /* 4 */ 52: ch  =  28; break;
2999         case /* 5 */ 53: ch  =  29; break;
3000         case /* 6 */ 54: ch  =  30; break;
3001         case /* 7 */ 55: ch  =  31; break;
3002         case /* 8 */ 56: ch  = 127; break;
3003         case /* ? */ 63: ch  = 127; break;
3004         default:         ch &=  31; break;
3005         }
3006       }
3007     }
3008     return String.fromCharCode(ch);
3009   } else {
3010     return undefined;
3011   }
3012 };
3013
3014 VT100.prototype.handleKey = function(event) {
3015   // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
3016   //            (event.shiftKey || event.ctrlKey || event.altKey ||
3017   //             event.metaKey ? ', ' +
3018   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3019   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3020   //            '\r\n');
3021   var ch, key;
3022   if (typeof event.charCode != 'undefined') {
3023     // non-IE keypress events have a translated charCode value. Also, our
3024     // fake events generated when receiving keydown events include this data
3025     // on all browsers.
3026     ch                                = event.charCode;
3027     key                               = event.keyCode;
3028   } else {
3029     // When sending a keypress event, IE includes the translated character
3030     // code in the keyCode field.
3031     ch                                = event.keyCode;
3032     key                               = undefined;
3033   }
3034
3035   // Apply modifier keys (ctrl and shift)
3036   if (ch) {
3037     key                               = undefined;
3038   }
3039   ch                                  = this.applyModifiers(ch, event);
3040
3041   // By this point, "ch" is either defined and contains the character code, or
3042   // it is undefined and "key" defines the code of a function key
3043   if (ch != undefined) {
3044     this.scrollable.scrollTop         = this.numScrollbackLines *
3045                                         this.cursorHeight + 1;
3046   } else {
3047     if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
3048       // Many programs have difficulties dealing with parametrized escape
3049       // sequences for function keys. Thus, if ALT is the only modifier
3050       // key, return Emacs-style keycodes for commonly used keys.
3051       switch (key) {
3052       case  33: /* Page Up      */ ch = '\u001B<';                      break;
3053       case  34: /* Page Down    */ ch = '\u001B>';                      break;
3054       case  37: /* Left         */ ch = '\u001Bb';                      break;
3055       case  38: /* Up           */ ch = '\u001Bp';                      break;
3056       case  39: /* Right        */ ch = '\u001Bf';                      break;
3057       case  40: /* Down         */ ch = '\u001Bn';                      break;
3058       case  46: /* Delete       */ ch = '\u001Bd';                      break;
3059       default:                                                          break;
3060       }
3061     } else if (event.shiftKey && !event.ctrlKey &&
3062                !event.altKey && !event.metaKey) {
3063       switch (key) {
3064       case  33: /* Page Up      */ this.scrollBack();                   return;
3065       case  34: /* Page Down    */ this.scrollFore();                   return;
3066       default:                                                          break;
3067       }
3068     }
3069     if (ch == undefined) {
3070       switch (key) {
3071       case   8: /* Backspace    */ ch = '\u007f';                       break;
3072       case   9: /* Tab          */ ch = '\u0009';                       break;
3073       case  10: /* Return       */ ch = '\u000A';                       break;
3074       case  13: /* Enter        */ ch = this.crLfMode ?
3075                                         '\r\n' : '\r';                  break;
3076       case  16: /* Shift        */                                      return;
3077       case  17: /* Ctrl         */                                      return;
3078       case  18: /* Alt          */                                      return;
3079       case  19: /* Break        */                                      return;
3080       case  20: /* Caps Lock    */                                      return;
3081       case  27: /* Escape       */ ch = '\u001B';                       break;
3082       case  33: /* Page Up      */ ch = '\u001B[5~';                    break;
3083       case  34: /* Page Down    */ ch = '\u001B[6~';                    break;
3084       case  35: /* End          */ ch = '\u001BOF';                     break;
3085       case  36: /* Home         */ ch = '\u001BOH';                     break;
3086       case  37: /* Left         */ ch = this.cursorKeyMode ?
3087                              '\u001BOD' : '\u001B[D';                   break;
3088       case  38: /* Up           */ ch = this.cursorKeyMode ?
3089                              '\u001BOA' : '\u001B[A';                   break;
3090       case  39: /* Right        */ ch = this.cursorKeyMode ?
3091                              '\u001BOC' : '\u001B[C';                   break;
3092       case  40: /* Down         */ ch = this.cursorKeyMode ?
3093                              '\u001BOB' : '\u001B[B';                   break;
3094       case  45: /* Insert       */ ch = '\u001B[2~';                    break;
3095       case  46: /* Delete       */ ch = '\u001B[3~';                    break;
3096       case  91: /* Left Window  */                                      return;
3097       case  92: /* Right Window */                                      return;
3098       case  93: /* Select       */                                      return;
3099       case  96: /* 0            */ ch = this.applyModifiers(48, event); break;
3100       case  97: /* 1            */ ch = this.applyModifiers(49, event); break;
3101       case  98: /* 2            */ ch = this.applyModifiers(50, event); break;
3102       case  99: /* 3            */ ch = this.applyModifiers(51, event); break;
3103       case 100: /* 4            */ ch = this.applyModifiers(52, event); break;
3104       case 101: /* 5            */ ch = this.applyModifiers(53, event); break;
3105       case 102: /* 6            */ ch = this.applyModifiers(54, event); break;
3106       case 103: /* 7            */ ch = this.applyModifiers(55, event); break;
3107       case 104: /* 8            */ ch = this.applyModifiers(56, event); break;
3108       case 105: /* 9            */ ch = this.applyModifiers(58, event); break;
3109       case 106: /* *            */ ch = this.applyModifiers(42, event); break;
3110       case 107: /* +            */ ch = this.applyModifiers(43, event); break;
3111       case 109: /* -            */ ch = this.applyModifiers(45, event); break;
3112       case 110: /* .            */ ch = this.applyModifiers(46, event); break;
3113       case 111: /* /            */ ch = this.applyModifiers(47, event); break;
3114       case 112: /* F1           */ ch = '\u001BOP';                     break;
3115       case 113: /* F2           */ ch = '\u001BOQ';                     break;
3116       case 114: /* F3           */ ch = '\u001BOR';                     break;
3117       case 115: /* F4           */ ch = '\u001BOS';                     break;
3118       case 116: /* F5           */ ch = '\u001B[15~';                   break;
3119       case 117: /* F6           */ ch = '\u001B[17~';                   break;
3120       case 118: /* F7           */ ch = '\u001B[18~';                   break;
3121       case 119: /* F8           */ ch = '\u001B[19~';                   break;
3122       case 120: /* F9           */ ch = '\u001B[20~';                   break;
3123       case 121: /* F10          */ ch = '\u001B[21~';                   break;
3124       case 122: /* F11          */ ch = '\u001B[23~';                   break;
3125       case 123: /* F12          */ ch = '\u001B[24~';                   break;
3126       case 144: /* Num Lock     */                                      return;
3127       case 145: /* Scroll Lock  */                                      return;
3128       case 186: /* ;            */ ch = this.applyModifiers(59, event); break;
3129       case 187: /* =            */ ch = this.applyModifiers(61, event); break;
3130       case 188: /* ,            */ ch = this.applyModifiers(44, event); break;
3131       case 189: /* -            */ ch = this.applyModifiers(45, event); break;
3132       case 173: /* -            */ ch = this.applyModifiers(45, event); break; // FF15 Patch
3133       case 190: /* .            */ ch = this.applyModifiers(46, event); break;
3134       case 191: /* /            */ ch = this.applyModifiers(47, event); break;
3135       // Conflicts with dead key " on Swiss keyboards
3136       //case 192: /* `            */ ch = this.applyModifiers(96, event); break;
3137       // Conflicts with dead key " on Swiss keyboards
3138       //case 219: /* [            */ ch = this.applyModifiers(91, event); break;
3139       case 220: /* \            */ ch = this.applyModifiers(92, event); break;
3140       // Conflicts with dead key ^ and ` on Swiss keaboards
3141       //                         ^ and " on French keyboards
3142       //case 221: /* ]            */ ch = this.applyModifiers(93, event); break;
3143       case 222: /* '            */ ch = this.applyModifiers(39, event); break;
3144       default:                                                          return;
3145       }
3146       this.scrollable.scrollTop       = this.numScrollbackLines *
3147                                         this.cursorHeight + 1;
3148     }
3149   }
3150
3151   // "ch" now contains the sequence of keycodes to send. But we might still
3152   // have to apply the effects of modifier keys.
3153   if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
3154     var start, digit, part1, part2;
3155     if ((start = ch.substr(0, 2)) == '\u001B[') {
3156       for (part1 = start;
3157            part1.length < ch.length &&
3158              (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
3159         part1                         = ch.substr(0, part1.length + 1);
3160       }
3161       part2                           = ch.substr(part1.length);
3162       if (part1.length > 2) {
3163         part1                        += ';';
3164       }
3165     } else if (start == '\u001BO') {
3166       part1                           = start;
3167       part2                           = ch.substr(2);
3168     }
3169     if (part1 != undefined) {
3170       ch                              = part1                                 +
3171                                        ((event.shiftKey             ? 1 : 0)  +
3172                                         (event.altKey|event.metaKey ? 2 : 0)  +
3173                                         (event.ctrlKey              ? 4 : 0)) +
3174                                         part2;
3175     } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
3176       ch                              = '\u001B' + ch;
3177     }
3178   }
3179
3180   if (this.menu.style.visibility == 'hidden') {
3181     // this.vt100('R: c=');
3182     // for (var i = 0; i < ch.length; i++)
3183     //   this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
3184     // this.vt100('\r\n');
3185     this.keysPressed(ch);
3186   }
3187 };
3188
3189 VT100.prototype.inspect = function(o, d) {
3190   if (d == undefined) {
3191     d       = 0;
3192   }
3193   var rc    = '';
3194   if (typeof o == 'object' && ++d < 2) {
3195     rc      = '[\r\n';
3196     for (i in o) {
3197       rc   += this.spaces(d * 2) + i + ' -> ';
3198       try {
3199         rc += this.inspect(o[i], d);
3200       } catch (e) {
3201         rc += '?' + '?' + '?\r\n';
3202       }
3203     }
3204     rc     += ']\r\n';
3205   } else {
3206     rc     += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
3207   }
3208   return rc;
3209 };
3210
3211 VT100.prototype.checkComposedKeys = function(event) {
3212   // Composed keys (at least on Linux) do not generate normal events.
3213   // Instead, they get entered into the text field. We normally catch
3214   // this on the next keyup event.
3215   var s              = this.input.value;
3216   if (s.length) {
3217     this.input.value = '';
3218     if (this.menu.style.visibility == 'hidden') {
3219       this.keysPressed(s);
3220     }
3221   }
3222 };
3223
3224 VT100.prototype.fixEvent = function(event) {
3225   // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
3226   // is used as a second-level selector, clear the modifier bits before
3227   // handling the event.
3228   if (event.ctrlKey && event.altKey) {
3229     var fake                = [ ];
3230     fake.charCode           = event.charCode;
3231     fake.keyCode            = event.keyCode;
3232     fake.ctrlKey            = false;
3233     fake.shiftKey           = event.shiftKey;
3234     fake.altKey             = false;
3235     fake.metaKey            = event.metaKey;
3236     return fake;
3237   }
3238
3239   // Some browsers fail to translate keys, if both shift and alt/meta is
3240   // pressed at the same time. We try to translate those cases, but that
3241   // only works for US keyboard layouts.
3242   if (event.shiftKey) {
3243     var u                   = undefined;
3244     var s                   = undefined;
3245     switch (this.lastNormalKeyDownEvent.keyCode) {
3246     case  39: /* ' -> " */ u = 39; s =  34; break;
3247     case  44: /* , -> < */ u = 44; s =  60; break;
3248     case  45: /* - -> _ */ u = 45; s =  95; break;
3249     case  46: /* . -> > */ u = 46; s =  62; break;
3250     case  47: /* / -> ? */ u = 47; s =  63; break;
3251
3252     case  48: /* 0 -> ) */ u = 48; s =  41; break;
3253     case  49: /* 1 -> ! */ u = 49; s =  33; break;
3254     case  50: /* 2 -> @ */ u = 50; s =  64; break;
3255     case  51: /* 3 -> # */ u = 51; s =  35; break;
3256     case  52: /* 4 -> $ */ u = 52; s =  36; break;
3257     case  53: /* 5 -> % */ u = 53; s =  37; break;
3258     case  54: /* 6 -> ^ */ u = 54; s =  94; break;
3259     case  55: /* 7 -> & */ u = 55; s =  38; break;
3260     case  56: /* 8 -> * */ u = 56; s =  42; break;
3261     case  57: /* 9 -> ( */ u = 57; s =  40; break;
3262
3263     case  59: /* ; -> : */ u = 59; s =  58; break;
3264     case  61: /* = -> + */ u = 61; s =  43; break;
3265     case  91: /* [ -> { */ u = 91; s = 123; break;
3266     case  92: /* \ -> | */ u = 92; s = 124; break;
3267     case  93: /* ] -> } */ u = 93; s = 125; break;
3268     case  96: /* ` -> ~ */ u = 96; s = 126; break;
3269
3270     case 109: /* - -> _ */ u = 45; s =  95; break;
3271     case 111: /* / -> ? */ u = 47; s =  63; break;
3272
3273     case 186: /* ; -> : */ u = 59; s =  58; break;
3274     case 187: /* = -> + */ u = 61; s =  43; break;
3275     case 188: /* , -> < */ u = 44; s =  60; break;
3276     case 189: /* - -> _ */ u = 45; s =  95; break;
3277     case 173: /* - -> _ */ u = 45; s =  95; break; // FF15 Patch
3278     case 190: /* . -> > */ u = 46; s =  62; break;
3279     case 191: /* / -> ? */ u = 47; s =  63; break;
3280     case 192: /* ` -> ~ */ u = 96; s = 126; break;
3281     case 219: /* [ -> { */ u = 91; s = 123; break;
3282     case 220: /* \ -> | */ u = 92; s = 124; break;
3283     case 221: /* ] -> } */ u = 93; s = 125; break;
3284     case 222: /* ' -> " */ u = 39; s =  34; break;
3285     default:                                break;
3286     }
3287     if (s && (event.charCode == u || event.charCode == 0)) {
3288       var fake              = [ ];
3289       fake.charCode         = s;
3290       fake.keyCode          = event.keyCode;
3291       fake.ctrlKey          = event.ctrlKey;
3292       fake.shiftKey         = event.shiftKey;
3293       fake.altKey           = event.altKey;
3294       fake.metaKey          = event.metaKey;
3295       return fake;
3296     }
3297   }
3298   return event;
3299 };
3300
3301 VT100.prototype.keyDown = function(event) {
3302   // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
3303   //            (event.shiftKey || event.ctrlKey || event.altKey ||
3304   //             event.metaKey ? ', ' +
3305   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3306   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3307   //            '\r\n');
3308   this.checkComposedKeys(event);
3309   this.lastKeyPressedEvent      = undefined;
3310   this.lastKeyDownEvent         = undefined;
3311   this.lastNormalKeyDownEvent   = event;
3312
3313   // Swiss keyboard conflicts:
3314   // [ 59
3315   // ] 192
3316   // ' 219 (dead key)
3317   // { 220
3318   // ~ 221 (dead key)
3319   // } 223
3320   // French keyoard conflicts:
3321   // ~ 50 (dead key)
3322   // } 107
3323   var asciiKey                  =
3324     event.keyCode ==  32                         ||
3325     event.keyCode >=  48 && event.keyCode <=  57 ||
3326     event.keyCode >=  65 && event.keyCode <=  90;
3327   var alphNumKey                =
3328     asciiKey                                     ||
3329     event.keyCode ==  59 ||
3330     event.keyCode >=  96 && event.keyCode <= 105 ||
3331     event.keyCode == 107 ||
3332     event.keyCode == 192 ||
3333     event.keyCode >= 219 && event.keyCode <= 221 ||
3334     event.keyCode == 223 ||
3335     event.keyCode == 226;
3336   var normalKey                 =
3337     alphNumKey                                   ||
3338     event.keyCode ==  61 ||
3339     event.keyCode == 106 ||
3340     event.keyCode >= 109 && event.keyCode <= 111 ||
3341     event.keyCode >= 186 && event.keyCode <= 191 ||
3342     event.keyCode == 222 ||
3343     event.keyCode == 252;
3344   try {
3345     if (navigator.appName == 'Konqueror') {
3346       normalKey                |= event.keyCode < 128;
3347     }
3348   } catch (e) {
3349   }
3350
3351   // We normally prefer to look at keypress events, as they perform the
3352   // translation from keyCode to charCode. This is important, as the
3353   // translation is locale-dependent.
3354   // But for some keys, we must intercept them during the keydown event,
3355   // as they would otherwise get interpreted by the browser.
3356   // Even, when doing all of this, there are some keys that we can never
3357   // intercept. This applies to some of the menu navigation keys in IE.
3358   // In fact, we see them, but we cannot stop IE from seeing them, too.
3359   if ((event.charCode || event.keyCode) &&
3360       ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3361         !event.shiftKey &&
3362         // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3363         // interpret this sequence ourselves, as some keyboard layouts use
3364         // it for second-level layouts.
3365         !(event.ctrlKey && event.altKey)) ||
3366        this.catchModifiersEarly && normalKey && !alphNumKey &&
3367        (event.ctrlKey || event.altKey || event.metaKey) ||
3368        !normalKey)) {
3369     this.lastKeyDownEvent       = event;
3370     var fake                    = [ ];
3371     fake.ctrlKey                = event.ctrlKey;
3372     fake.shiftKey               = event.shiftKey;
3373     fake.altKey                 = event.altKey;
3374     fake.metaKey                = event.metaKey;
3375     if (asciiKey) {
3376       fake.charCode             = event.keyCode;
3377       fake.keyCode              = 0;
3378     } else {
3379       fake.charCode             = 0;
3380       fake.keyCode              = event.keyCode;
3381       if (!alphNumKey && event.shiftKey) {
3382         fake                    = this.fixEvent(fake);
3383       }
3384     }
3385
3386     this.handleKey(fake);
3387     this.lastNormalKeyDownEvent = undefined;
3388
3389     try {
3390       // For non-IE browsers
3391       event.stopPropagation();
3392       event.preventDefault();
3393     } catch (e) {
3394     }
3395     try {
3396       // For IE
3397       event.cancelBubble = true;
3398       event.returnValue  = false;
3399       event.keyCode      = 0;
3400     } catch (e) {
3401     }
3402
3403     return false;
3404   }
3405   return true;
3406 };
3407
3408 VT100.prototype.keyPressed = function(event) {
3409   // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3410   //            (event.shiftKey || event.ctrlKey || event.altKey ||
3411   //             event.metaKey ? ', ' +
3412   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3413   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3414   //            '\r\n');
3415   if (this.lastKeyDownEvent) {
3416     // If we already processed the key on keydown, do not process it
3417     // again here. Ideally, the browser should not even have generated a
3418     // keypress event in this case. But that does not appear to always work.
3419     this.lastKeyDownEvent     = undefined;
3420   } else {
3421     this.handleKey(event.altKey || event.metaKey
3422                    ? this.fixEvent(event) : event);
3423   }
3424
3425   try {
3426     // For non-IE browsers
3427     event.preventDefault();
3428   } catch (e) {
3429   }
3430
3431   try {
3432     // For IE
3433     event.cancelBubble = true;
3434     event.returnValue  = false;
3435     event.keyCode      = 0;
3436   } catch (e) {
3437   }
3438
3439   this.lastNormalKeyDownEvent = undefined;
3440   this.lastKeyPressedEvent    = event;
3441   return false;
3442 };
3443
3444 VT100.prototype.keyUp = function(event) {
3445   // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3446   //            (event.shiftKey || event.ctrlKey || event.altKey ||
3447   //             event.metaKey ? ', ' +
3448   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3449   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3450   //            '\r\n');
3451   if (this.lastKeyPressedEvent) {
3452     // The compose key on Linux occasionally confuses the browser and keeps
3453     // inserting bogus characters into the input field, even if just a regular
3454     // key has been pressed. Detect this case and drop the bogus characters.
3455     (event.target ||
3456      event.srcElement).value      = '';
3457   } else {
3458     // This is usually were we notice that a key has been composed and
3459     // thus failed to generate normal events.
3460     this.checkComposedKeys(event);
3461
3462     // Some browsers don't report keypress events if ctrl or alt is pressed
3463     // for non-alphanumerical keys. Patch things up for now, but in the
3464     // future we will catch these keys earlier (in the keydown handler).
3465     if (this.lastNormalKeyDownEvent) {
3466       // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3467       this.catchModifiersEarly    = true;
3468       var asciiKey                =
3469         event.keyCode ==  32                         ||
3470         // Conflicts with dead key ~ (code 50) on French keyboards
3471         //event.keyCode >=  48 && event.keyCode <=  57 ||
3472         event.keyCode >=  48 && event.keyCode <=  49 ||
3473         event.keyCode >=  51 && event.keyCode <=  57 ||
3474         event.keyCode >=  65 && event.keyCode <=  90;
3475       var alphNumKey              =
3476         asciiKey                                     ||
3477         event.keyCode ==  50                         ||
3478         event.keyCode >=  96 && event.keyCode <= 105;
3479       var normalKey               =
3480         alphNumKey                                   ||
3481         event.keyCode ==  59 || event.keyCode ==  61 ||
3482         event.keyCode == 106 || event.keyCode == 107 ||
3483         event.keyCode >= 109 && event.keyCode <= 111 ||
3484         event.keyCode >= 186 && event.keyCode <= 192 ||
3485         event.keyCode >= 219 && event.keyCode <= 223 ||
3486         event.keyCode == 252;
3487       var fake                    = [ ];
3488       fake.ctrlKey                = event.ctrlKey;
3489       fake.shiftKey               = event.shiftKey;
3490       fake.altKey                 = event.altKey;
3491       fake.metaKey                = event.metaKey;
3492       if (asciiKey) {
3493         fake.charCode             = event.keyCode;
3494         fake.keyCode              = 0;
3495       } else {
3496         fake.charCode             = 0;
3497         fake.keyCode              = event.keyCode;
3498         if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3499           fake                    = this.fixEvent(fake);
3500         }
3501       }
3502       this.lastNormalKeyDownEvent = undefined;
3503       this.handleKey(fake);
3504     }
3505   }
3506
3507   try {
3508     // For IE
3509     event.cancelBubble            = true;
3510     event.returnValue             = false;
3511     event.keyCode                 = 0;
3512   } catch (e) {
3513   }
3514
3515   this.lastKeyDownEvent           = undefined;
3516   this.lastKeyPressedEvent        = undefined;
3517   return false;
3518 };
3519
3520 VT100.prototype.animateCursor = function(inactive) {
3521   if (!this.cursorInterval) {
3522     this.cursorInterval       = setInterval(
3523       function(vt100) {
3524         return function() {
3525           vt100.animateCursor();
3526
3527           // Use this opportunity to check whether the user entered a composed
3528           // key, or whether somebody pasted text into the textfield.
3529           vt100.checkComposedKeys();
3530         }
3531       }(this), 500);
3532   }
3533   if (inactive != undefined || this.cursor.className != 'inactive') {
3534     if (inactive) {
3535       this.cursor.className   = 'inactive';
3536     } else {
3537       if (this.blinkingCursor) {
3538         this.cursor.className = this.cursor.className == 'bright'
3539                                 ? 'dim' : 'bright';
3540       } else {
3541         this.cursor.className = 'bright';
3542       }
3543     }
3544   }
3545 };
3546
3547 VT100.prototype.blurCursor = function() {
3548   this.animateCursor(true);
3549 };
3550
3551 VT100.prototype.focusCursor = function() {
3552   this.animateCursor(false);
3553 };
3554
3555 VT100.prototype.flashScreen = function() {
3556   this.isInverted       = !this.isInverted;
3557   this.refreshInvertedState();
3558   this.isInverted       = !this.isInverted;
3559   setTimeout(function(vt100) {
3560                return function() {
3561                  vt100.refreshInvertedState();
3562                };
3563              }(this), 100);
3564 };
3565
3566 VT100.prototype.beep = function() {
3567   if (this.visualBell) {
3568     this.flashScreen();
3569   } else {
3570     try {
3571       this.beeper.Play();
3572     } catch (e) {
3573       try {
3574         this.beeper.src = 'beep.wav';
3575       } catch (e) {
3576       }
3577     }
3578   }
3579 };
3580
3581 VT100.prototype.bs = function() {
3582   if (this.cursorX > 0) {
3583     this.gotoXY(this.cursorX - 1, this.cursorY);
3584     this.needWrap = false;
3585   }
3586 };
3587
3588 VT100.prototype.ht = function(count) {
3589   if (count == undefined) {
3590     count        = 1;
3591   }
3592   var cx         = this.cursorX;
3593   while (count-- > 0) {
3594     while (cx++ < this.terminalWidth) {
3595       var tabState = this.userTabStop[cx];
3596       if (tabState == false) {
3597         // Explicitly cleared tab stop
3598         continue;
3599       } else if (tabState) {
3600         // Explicitly set tab stop
3601         break;
3602       } else {
3603         // Default tab stop at each eighth column
3604         if (cx % 8 == 0) {
3605           break;
3606         }
3607       }
3608     }
3609   }
3610   if (cx > this.terminalWidth - 1) {
3611     cx           = this.terminalWidth - 1;
3612   }
3613   if (cx != this.cursorX) {
3614     this.gotoXY(cx, this.cursorY);
3615   }
3616 };
3617
3618 VT100.prototype.rt = function(count) {
3619   if (count == undefined) {
3620     count          = 1 ;
3621   }
3622   var cx           = this.cursorX;
3623   while (count-- > 0) {
3624     while (cx-- > 0) {
3625       var tabState = this.userTabStop[cx];
3626       if (tabState == false) {
3627         // Explicitly cleared tab stop
3628         continue;
3629       } else if (tabState) {
3630         // Explicitly set tab stop
3631         break;
3632       } else {
3633         // Default tab stop at each eighth column
3634         if (cx % 8 == 0) {
3635           break;
3636         }
3637       }
3638     }
3639   }
3640   if (cx < 0) {
3641     cx             = 0;
3642   }
3643   if (cx != this.cursorX) {
3644     this.gotoXY(cx, this.cursorY);
3645   }
3646 };
3647
3648 VT100.prototype.cr = function() {
3649   this.gotoXY(0, this.cursorY);
3650   this.needWrap = false;
3651 };
3652
3653 VT100.prototype.lf = function(count) {
3654   if (count == undefined) {
3655     count    = 1;
3656   } else {
3657     if (count > this.terminalHeight) {
3658       count  = this.terminalHeight;
3659     }
3660     if (count < 1) {
3661       count  = 1;
3662     }
3663   }
3664   while (count-- > 0) {
3665     if (this.cursorY == this.bottom - 1) {
3666       this.scrollRegion(0, this.top + 1,
3667                         this.terminalWidth, this.bottom - this.top - 1,
3668                         0, -1, this.color, this.style);
3669       offset = undefined;
3670     } else if (this.cursorY < this.terminalHeight - 1) {
3671       this.gotoXY(this.cursorX, this.cursorY + 1);
3672     }
3673   }
3674 };
3675
3676 VT100.prototype.ri = function(count) {
3677   if (count == undefined) {
3678     count   = 1;
3679   } else {
3680     if (count > this.terminalHeight) {
3681       count = this.terminalHeight;
3682     }
3683     if (count < 1) {
3684       count = 1;
3685     }
3686   }
3687   while (count-- > 0) {
3688     if (this.cursorY == this.top) {
3689       this.scrollRegion(0, this.top,
3690                         this.terminalWidth, this.bottom - this.top - 1,
3691                         0, 1, this.color, this.style);
3692     } else if (this.cursorY > 0) {
3693       this.gotoXY(this.cursorX, this.cursorY - 1);
3694     }
3695   }
3696   this.needWrap = false;
3697 };
3698
3699 VT100.prototype.respondID = function() {
3700   this.respondString += '\u001B[?6c';
3701 };
3702
3703 VT100.prototype.respondSecondaryDA = function() {
3704   this.respondString += '\u001B[>0;0;0c';
3705 };
3706
3707
3708 VT100.prototype.updateStyle = function() {
3709   this.style   = '';
3710   if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3711     this.style = 'text-decoration: underline;';
3712   }
3713   var bg       = (this.attr >> 4) & 0xF;
3714   var fg       =  this.attr       & 0xF;
3715   if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3716     var tmp    = bg;
3717     bg         = fg;
3718     fg         = tmp;
3719   }
3720   if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3721     fg         = 8; // Dark grey
3722   } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3723     fg        |= 8;
3724     this.style = 'font-weight: bold;';
3725   }
3726   if (this.attr & 0x1000 /* ATTR_BLINK */) {
3727     this.style = 'text-decoration: blink;';
3728   }
3729   this.color   = 'ansi' + fg + ' bgAnsi' + bg;
3730 };
3731
3732 VT100.prototype.setAttrColors = function(attr) {
3733   if (attr != this.attr) {
3734     this.attr = attr;
3735     this.updateStyle();
3736   }
3737 };
3738
3739 VT100.prototype.saveCursor = function() {
3740   this.savedX[this.currentScreen]     = this.cursorX;
3741   this.savedY[this.currentScreen]     = this.cursorY;
3742   this.savedAttr[this.currentScreen]  = this.attr;
3743   this.savedUseGMap                   = this.useGMap;
3744   for (var i = 0; i < 4; i++) {
3745     this.savedGMap[i]                 = this.GMap[i];
3746   }
3747   this.savedValid[this.currentScreen] = true;
3748 };
3749
3750 VT100.prototype.restoreCursor = function() {
3751   if (!this.savedValid[this.currentScreen]) {
3752     return;
3753   }
3754   this.attr      = this.savedAttr[this.currentScreen];
3755   this.updateStyle();
3756   this.useGMap   = this.savedUseGMap;
3757   for (var i = 0; i < 4; i++) {
3758     this.GMap[i] = this.savedGMap[i];
3759   }
3760   this.translate = this.GMap[this.useGMap];
3761   this.needWrap  = false;
3762   this.gotoXY(this.savedX[this.currentScreen],
3763               this.savedY[this.currentScreen]);
3764 };
3765
3766 VT100.prototype.getTransformName = function() {
3767   var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3768   for (var i = 0; i < styles.length; ++i) {
3769     if (typeof this.console[0].style[styles[i]] != 'undefined') {
3770       return styles[i];
3771     }
3772   }
3773   return undefined;
3774 };
3775
3776 VT100.prototype.getTransformStyle = function(transform, scale) {
3777   return scale && scale != 1.0
3778     ? transform == 'filter'
3779       ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3780                                  'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3781                                  "sizingMethod='auto expand')"
3782       : 'translateX(-50%) ' +
3783         'scaleX(' + (1.0/scale) + ') ' +
3784         'translateX(50%)'
3785     : '';
3786 };
3787
3788 VT100.prototype.set80_132Mode = function(state) {
3789   var transform                  = this.getTransformName();
3790   if (transform) {
3791     if ((this.console[this.currentScreen].style[transform] != '') == state) {
3792       return;
3793     }
3794     var style                    = state ?
3795                                    this.getTransformStyle(transform, 1.65):'';
3796     this.console[this.currentScreen].style[transform] = style;
3797     this.cursor.style[transform] = style;
3798     this.space.style[transform]  = style;
3799     this.scale                   = state ? 1.65 : 1.0;
3800     if (transform == 'filter') {
3801       this.console[this.currentScreen].style.width = state ? '165%' : '';
3802     }
3803     this.resizer();
3804   }
3805 };
3806
3807 VT100.prototype.setMode = function(state) {
3808   for (var i = 0; i <= this.npar; i++) {
3809     if (this.isQuestionMark) {
3810       switch (this.par[i]) {
3811       case  1: this.cursorKeyMode      = state;                      break;
3812       case  3: this.set80_132Mode(state);                            break;
3813       case  5: this.isInverted = state; this.refreshInvertedState(); break;
3814       case  6: this.offsetMode         = state;                      break;
3815       case  7: this.autoWrapMode       = state;                      break;
3816       case 1000:
3817       case  9: this.mouseReporting     = state;                      break;
3818       case 25: this.cursorNeedsShowing = state;
3819                if (state) { this.showCursor(); }
3820                else       { this.hideCursor(); }                     break;
3821       case 1047:
3822       case 1049:
3823       case 47: this.enableAlternateScreen(state);                    break;
3824       default:                                                       break;
3825       }
3826     } else {
3827       switch (this.par[i]) {
3828       case  3: this.dispCtrl           = state;                      break;
3829       case  4: this.insertMode         = state;                      break;
3830       case  20:this.crLfMode           = state;                      break;
3831       default:                                                       break;
3832       }
3833     }
3834   }
3835 };
3836
3837 VT100.prototype.statusReport = function() {
3838   // Ready and operational.
3839   this.respondString += '\u001B[0n';
3840 };
3841
3842 VT100.prototype.cursorReport = function() {
3843   this.respondString += '\u001B[' +
3844                         (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3845                         ';' +
3846                         (this.cursorX + 1) +
3847                         'R';
3848 };
3849
3850 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3851   // Changing of cursor color is not implemented.
3852 };
3853
3854 VT100.prototype.openPrinterWindow = function() {
3855   var rc            = true;
3856   try {
3857     if (!this.printWin || this.printWin.closed) {
3858       this.printWin = window.open('', 'print-output',
3859         'width=800,height=600,directories=no,location=no,menubar=yes,' +
3860         'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3861       this.printWin.document.body.innerHTML =
3862         '<link rel="stylesheet" href="' +
3863           document.location.protocol + '//' + document.location.host +
3864           document.location.pathname.replace(/[^/]*$/, '') +
3865           'print-styles.css" type="text/css">\n' +
3866         '<div id="options"><input id="autoprint" type="checkbox"' +
3867           (this.autoprint ? ' checked' : '') + '>' +
3868           'Automatically, print page(s) when job is ready' +
3869         '</input></div>\n' +
3870         '<div id="spacer"><input type="checkbox">&nbsp;</input></div>' +
3871         '<pre id="print"></pre>\n';
3872       var autoprint = this.printWin.document.getElementById('autoprint');
3873       this.addListener(autoprint, 'click',
3874                        (function(vt100, autoprint) {
3875                          return function() {
3876                            vt100.autoprint = autoprint.checked;
3877                            vt100.storeUserSettings();
3878                            return false;
3879                          };
3880                        })(this, autoprint));
3881       this.printWin.document.title = 'ShellInABox Printer Output';
3882     }
3883   } catch (e) {
3884     // Maybe, a popup blocker prevented us from working. Better catch the
3885     // exception, so that we won't break the entire terminal session. The
3886     // user probably needs to disable the blocker first before retrying the
3887     // operation.
3888     rc              = false;
3889   }
3890   rc               &= this.printWin && !this.printWin.closed &&
3891                       (this.printWin.innerWidth ||
3892                        this.printWin.document.documentElement.clientWidth ||
3893                        this.printWin.document.body.clientWidth) > 1;
3894
3895   if (!rc && this.printing == 100) {
3896     // Different popup blockers work differently. We try to detect a couple
3897     // of common methods. And then we retry again a brief amount later, as
3898     // false positives are otherwise possible. If we are sure that there is
3899     // a popup blocker in effect, we alert the user to it. This is helpful
3900     // as some popup blockers have minimal or no UI, and the user might not
3901     // notice that they are missing the popup. In any case, we only show at
3902     // most one message per print job.
3903     this.printing   = true;
3904     setTimeout((function(win) {
3905                   return function() {
3906                     if (!win || win.closed ||
3907                         (win.innerWidth ||
3908                          win.document.documentElement.clientWidth ||
3909                          win.document.body.clientWidth) <= 1) {
3910                       alert('Attempted to print, but a popup blocker ' +
3911                             'prevented the printer window from opening');
3912                     }
3913                   };
3914                 })(this.printWin), 2000);
3915   }
3916   return rc;
3917 };
3918
3919 VT100.prototype.sendToPrinter = function(s) {
3920   this.openPrinterWindow();
3921   try {
3922     var doc   = this.printWin.document;
3923     var print = doc.getElementById('print');
3924     if (print.lastChild && print.lastChild.nodeName == '#text') {
3925       print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3926     } else {
3927       print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3928     }
3929   } catch (e) {
3930     // There probably was a more aggressive popup blocker that prevented us
3931     // from accessing the printer windows.
3932   }
3933 };
3934
3935 VT100.prototype.sendControlToPrinter = function(ch) {
3936   // We get called whenever doControl() is active. But for the printer, we
3937   // only implement a basic line printer that doesn't understand most of
3938   // the escape sequences of the VT100 terminal. In fact, the only escape
3939   // sequence that we really need to recognize is '^[[5i' for turning the
3940   // printer off.
3941   try {
3942     switch (ch) {
3943     case  9:
3944       // HT
3945       this.openPrinterWindow();
3946       var doc                 = this.printWin.document;
3947       var print               = doc.getElementById('print');
3948       var chars               = print.lastChild &&
3949                                 print.lastChild.nodeName == '#text' ?
3950                                 print.lastChild.textContent.length : 0;
3951       this.sendToPrinter(this.spaces(8 - (chars % 8)));
3952       break;
3953     case 10:
3954       // CR
3955       break;
3956     case 12:
3957       // FF
3958       this.openPrinterWindow();
3959       var pageBreak           = this.printWin.document.createElement('div');
3960       pageBreak.className     = 'pagebreak';
3961       pageBreak.innerHTML     = '<hr />';
3962       this.printWin.document.getElementById('print').appendChild(pageBreak);
3963       break;
3964     case 13:
3965       // LF
3966       this.openPrinterWindow();
3967       var lineBreak           = this.printWin.document.createElement('br');
3968       this.printWin.document.getElementById('print').appendChild(lineBreak);
3969       break;
3970     case 27:
3971       // ESC
3972       this.isEsc              = 1 /* ESesc */;
3973       break;
3974     default:
3975       switch (this.isEsc) {
3976       case 1 /* ESesc */:
3977         this.isEsc            = 0 /* ESnormal */;
3978         switch (ch) {
3979         case 0x5B /*[*/:
3980           this.isEsc          = 2 /* ESsquare */;
3981           break;
3982         default:
3983           break;
3984         }
3985         break;
3986       case 2 /* ESsquare */:
3987         this.npar             = 0;
3988         this.par              = [ 0, 0, 0, 0, 0, 0, 0, 0,
3989                                   0, 0, 0, 0, 0, 0, 0, 0 ];
3990         this.isEsc            = 3 /* ESgetpars */;
3991         this.isQuestionMark   = ch == 0x3F /*?*/;
3992         if (this.isQuestionMark) {
3993           break;
3994         }
3995         // Fall through
3996       case 3 /* ESgetpars */:
3997         if (ch == 0x3B /*;*/) {
3998           this.npar++;
3999           break;
4000         } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4001           var par             = this.par[this.npar];
4002           if (par == undefined) {
4003             par               = 0;
4004           }
4005           this.par[this.npar] = 10*par + (ch & 0xF);
4006           break;
4007         } else {
4008           this.isEsc          = 4 /* ESgotpars */;
4009         }
4010         // Fall through
4011       case 4 /* ESgotpars */:
4012         this.isEsc            = 0 /* ESnormal */;
4013         if (this.isQuestionMark) {
4014           break;
4015         }
4016         switch (ch) {
4017         case 0x69 /*i*/:
4018           this.csii(this.par[0]);
4019           break;
4020         default:
4021           break;
4022         }
4023         break;
4024       default:
4025         this.isEsc            = 0 /* ESnormal */;
4026         break;
4027       }
4028       break;
4029     }
4030   } catch (e) {
4031     // There probably was a more aggressive popup blocker that prevented us
4032     // from accessing the printer windows.
4033   }
4034 };
4035
4036 VT100.prototype.csiAt = function(number) {
4037   // Insert spaces
4038   if (number == 0) {
4039     number      = 1;
4040   }
4041   if (number > this.terminalWidth - this.cursorX) {
4042     number      = this.terminalWidth - this.cursorX;
4043   }
4044   this.scrollRegion(this.cursorX, this.cursorY,
4045                     this.terminalWidth - this.cursorX - number, 1,
4046                     number, 0, this.color, this.style);
4047   this.needWrap = false;
4048 };
4049
4050 VT100.prototype.csii = function(number) {
4051   // Printer control
4052   switch (number) {
4053   case 0: // Print Screen
4054     window.print();
4055     break;
4056   case 4: // Stop printing
4057     try {
4058       if (this.printing && this.printWin && !this.printWin.closed) {
4059         var print = this.printWin.document.getElementById('print');
4060         while (print.lastChild &&
4061                print.lastChild.tagName == 'DIV' &&
4062                print.lastChild.className == 'pagebreak') {
4063           // Remove trailing blank pages
4064           print.removeChild(print.lastChild);
4065         }
4066         if (this.autoprint) {
4067           this.printWin.print();
4068         }
4069       }
4070     } catch (e) {
4071     }
4072     this.printing = false;
4073     break;
4074   case 5: // Start printing
4075     if (!this.printing && this.printWin && !this.printWin.closed) {
4076       this.printWin.document.getElementById('print').innerHTML = '';
4077     }
4078     this.printing = 100;
4079     break;
4080   default:
4081     break;
4082   }
4083 };
4084
4085 VT100.prototype.csiJ = function(number) {
4086   switch (number) {
4087   case 0: // Erase from cursor to end of display
4088     this.clearRegion(this.cursorX, this.cursorY,
4089                      this.terminalWidth - this.cursorX, 1,
4090                      this.color, this.style);
4091     if (this.cursorY < this.terminalHeight-2) {
4092       this.clearRegion(0, this.cursorY+1,
4093                        this.terminalWidth, this.terminalHeight-this.cursorY-1,
4094                        this.color, this.style);
4095     }
4096     break;
4097   case 1: // Erase from start to cursor
4098     if (this.cursorY > 0) {
4099       this.clearRegion(0, 0,
4100                        this.terminalWidth, this.cursorY,
4101                        this.color, this.style);
4102     }
4103     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4104                      this.color, this.style);
4105     break;
4106   case 2: // Erase whole display
4107     this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
4108                      this.color, this.style);
4109     break;
4110   default:
4111     return;
4112   }
4113   needWrap = false;
4114 };
4115
4116 VT100.prototype.csiK = function(number) {
4117   switch (number) {
4118   case 0: // Erase from cursor to end of line
4119     this.clearRegion(this.cursorX, this.cursorY,
4120                      this.terminalWidth - this.cursorX, 1,
4121                      this.color, this.style);
4122     break;
4123   case 1: // Erase from start of line to cursor
4124     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
4125                      this.color, this.style);
4126     break;
4127   case 2: // Erase whole line
4128     this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
4129                      this.color, this.style);
4130     break;
4131   default:
4132     return;
4133   }
4134   needWrap = false;
4135 };
4136
4137 VT100.prototype.csiL = function(number) {
4138   // Open line by inserting blank line(s)
4139   if (this.cursorY >= this.bottom) {
4140     return;
4141   }
4142   if (number == 0) {
4143     number = 1;
4144   }
4145   if (number > this.bottom - this.cursorY) {
4146     number = this.bottom - this.cursorY;
4147   }
4148   this.scrollRegion(0, this.cursorY,
4149                     this.terminalWidth, this.bottom - this.cursorY - number,
4150                     0, number, this.color, this.style);
4151   needWrap = false;
4152 };
4153
4154 VT100.prototype.csiM = function(number) {
4155   // Delete line(s), scrolling up the bottom of the screen.
4156   if (this.cursorY >= this.bottom) {
4157     return;
4158   }
4159   if (number == 0) {
4160     number = 1;
4161   }
4162   if (number > this.bottom - this.cursorY) {
4163     number = bottom - cursorY;
4164   }
4165   this.scrollRegion(0, this.cursorY + number,
4166                     this.terminalWidth, this.bottom - this.cursorY - number,
4167                     0, -number, this.color, this.style);
4168   needWrap = false;
4169 };
4170
4171 VT100.prototype.csim = function() {
4172   for (var i = 0; i <= this.npar; i++) {
4173     switch (this.par[i]) {
4174     case 0:  this.attr  = 0x00F0 /* ATTR_DEFAULT */;                                break;
4175     case 1:  this.attr  = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */;         break;
4176     case 2:  this.attr  = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */;         break;
4177     case 4:  this.attr |= 0x0200 /* ATTR_UNDERLINE */;                              break;
4178     case 5:  this.attr |= 0x1000 /* ATTR_BLINK */;                                  break;
4179     case 7:  this.attr |= 0x0100 /* ATTR_REVERSE */;                                break;
4180     case 10:
4181       this.translate    = this.GMap[this.useGMap];
4182       this.dispCtrl     = false;
4183       this.toggleMeta   = false;
4184       break;
4185     case 11:
4186       this.translate    = this.CodePage437Map;
4187       this.dispCtrl     = true;
4188       this.toggleMeta   = false;
4189       break;
4190     case 12:
4191       this.translate    = this.CodePage437Map;
4192       this.dispCtrl     = true;
4193       this.toggleMeta   = true;
4194       break;
4195     case 21:
4196     case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */);                     break;
4197     case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */;                            break;
4198     case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */;                                break;
4199     case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */;                              break;
4200     case 38: this.attr  = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
4201                           0x0200 /* ATTR_UNDERLINE */;                              break;
4202     case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
4203     case 49: this.attr |= 0xF0;                                        break;
4204     default:
4205       if (this.par[i] >= 30 && this.par[i] <= 37) {
4206           var fg        = this.par[i] - 30;
4207           this.attr     = (this.attr & ~0x0F) | fg;
4208       } else if (this.par[i] >= 40 && this.par[i] <= 47) {
4209           var bg        = this.par[i] - 40;
4210           this.attr     = (this.attr & ~0xF0) | (bg << 4);
4211       }
4212       break;
4213     }
4214   }
4215   this.updateStyle();
4216 };
4217
4218 VT100.prototype.csiP = function(number) {
4219   // Delete character(s) following cursor
4220   if (number == 0) {
4221     number = 1;
4222   }
4223   if (number > this.terminalWidth - this.cursorX) {
4224     number = this.terminalWidth - this.cursorX;
4225   }
4226   this.scrollRegion(this.cursorX + number, this.cursorY,
4227                     this.terminalWidth - this.cursorX - number, 1,
4228                     -number, 0, this.color, this.style);
4229   needWrap = false;
4230 };
4231
4232 VT100.prototype.csiX = function(number) {
4233   // Clear characters following cursor
4234   if (number == 0) {
4235     number++;
4236   }
4237   if (number > this.terminalWidth - this.cursorX) {
4238     number = this.terminalWidth - this.cursorX;
4239   }
4240   this.clearRegion(this.cursorX, this.cursorY, number, 1,
4241                    this.color, this.style);
4242   needWrap = false;
4243 };
4244
4245 VT100.prototype.settermCommand = function() {
4246   // Setterm commands are not implemented
4247 };
4248
4249 VT100.prototype.doControl = function(ch) {
4250   if (this.printing) {
4251     this.sendControlToPrinter(ch);
4252     return '';
4253   }
4254   var lineBuf                = '';
4255   switch (ch) {
4256   case 0x00: /* ignored */                                              break;
4257   case 0x08: this.bs();                                                 break;
4258   case 0x09: this.ht();                                                 break;
4259   case 0x0A:
4260   case 0x0B:
4261   case 0x0C:
4262   case 0x84: this.lf(); if (!this.crLfMode)                             break;
4263   case 0x0D: this.cr();                                                 break;
4264   case 0x85: this.cr(); this.lf();                                      break;
4265   case 0x0E: this.useGMap     = 1;
4266              this.translate   = this.GMap[1];
4267              this.dispCtrl    = true;                                   break;
4268   case 0x0F: this.useGMap     = 0;
4269              this.translate   = this.GMap[0];
4270              this.dispCtrl    = false;                                  break;
4271   case 0x18:
4272   case 0x1A: this.isEsc       = 0 /* ESnormal */;                               break;
4273   case 0x1B: this.isEsc       = 1 /* ESesc */;                                  break;
4274   case 0x7F: /* ignored */                                              break;
4275   case 0x88: this.userTabStop[this.cursorX] = true;                     break;
4276   case 0x8D: this.ri();                                                 break;
4277   case 0x8E: this.isEsc       = 18 /* ESss2 */;                                  break;
4278   case 0x8F: this.isEsc       = 19 /* ESss3 */;                                  break;
4279   case 0x9A: this.respondID();                                          break;
4280   case 0x9B: this.isEsc       = 2 /* ESsquare */;                               break;
4281   case 0x07: if (this.isEsc != 17 /* EStitle */) {
4282                this.beep();                                             break;
4283              }
4284              /* fall thru */
4285   default:   switch (this.isEsc) {
4286     case 1 /* ESesc */:
4287       this.isEsc              = 0 /* ESnormal */;
4288       switch (ch) {
4289 /*%*/ case 0x25: this.isEsc   = 13 /* ESpercent */;                              break;
4290 /*(*/ case 0x28: this.isEsc   = 8 /* ESsetG0 */;                                break;
4291 /*-*/ case 0x2D:
4292 /*)*/ case 0x29: this.isEsc   = 9 /* ESsetG1 */;                                break;
4293 /*.*/ case 0x2E:
4294 /***/ case 0x2A: this.isEsc   = 10 /* ESsetG2 */;                                break;
4295 /*/*/ case 0x2F:
4296 /*+*/ case 0x2B: this.isEsc   = 11 /* ESsetG3 */;                                break;
4297 /*#*/ case 0x23: this.isEsc   = 7 /* EShash */;                                 break;
4298 /*7*/ case 0x37: this.saveCursor();                                     break;
4299 /*8*/ case 0x38: this.restoreCursor();                                  break;
4300 /*>*/ case 0x3E: this.applKeyMode = false;                              break;
4301 /*=*/ case 0x3D: this.applKeyMode = true;                               break;
4302 /*D*/ case 0x44: this.lf();                                             break;
4303 /*E*/ case 0x45: this.cr(); this.lf();                                  break;
4304 /*M*/ case 0x4D: this.ri();                                             break;
4305 /*N*/ case 0x4E: this.isEsc   = 18 /* ESss2 */;                                  break;
4306 /*O*/ case 0x4F: this.isEsc   = 19 /* ESss3 */;                                  break;
4307 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true;                 break;
4308 /*Z*/ case 0x5A: this.respondID();                                      break;
4309 /*[*/ case 0x5B: this.isEsc   = 2 /* ESsquare */;                               break;
4310 /*]*/ case 0x5D: this.isEsc   = 15 /* ESnonstd */;                               break;
4311 /*c*/ case 0x63: this.reset();                                          break;
4312 /*g*/ case 0x67: this.flashScreen();                                    break;
4313       default:                                                          break;
4314       }
4315       break;
4316     case 15 /* ESnonstd */:
4317       switch (ch) {
4318 /*0*/ case 0x30:
4319 /*1*/ case 0x31:
4320 /*2*/ case 0x32: this.isEsc   = 17 /* EStitle */; this.titleString = '';         break;
4321 /*P*/ case 0x50: this.npar    = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4322                  this.isEsc   = 16 /* ESpalette */;                              break;
4323 /*R*/ case 0x52: // Palette support is not implemented
4324                  this.isEsc   = 0 /* ESnormal */;                               break;
4325       default:   this.isEsc   = 0 /* ESnormal */;                               break;
4326       }
4327       break;
4328     case 16 /* ESpalette */:
4329       if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4330           (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4331           (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4332         this.par[this.npar++] = ch > 0x39  /*9*/ ? (ch & 0xDF) - 55
4333                                                 : (ch & 0xF);
4334         if (this.npar == 7) {
4335           // Palette support is not implemented
4336           this.isEsc          = 0 /* ESnormal */;
4337         }
4338       } else {
4339         this.isEsc            = 0 /* ESnormal */;
4340       }
4341       break;
4342     case 2 /* ESsquare */:
4343       this.npar               = 0;
4344       this.par                = [ 0, 0, 0, 0, 0, 0, 0, 0,
4345                                   0, 0, 0, 0, 0, 0, 0, 0 ];
4346       this.isEsc              = 3 /* ESgetpars */;
4347 /*[*/ if (ch == 0x5B) { // Function key
4348         this.isEsc            = 6 /* ESfunckey */;
4349         break;
4350       } else {
4351 /*?*/   this.isQuestionMark   = ch == 0x3F;
4352         if (this.isQuestionMark) {
4353           break;
4354         }
4355       }
4356       // Fall through
4357     case 5 /* ESdeviceattr */:
4358     case 3 /* ESgetpars */:
4359 /*;*/ if (ch == 0x3B) {
4360         this.npar++;
4361         break;
4362       } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4363         var par               = this.par[this.npar];
4364         if (par == undefined) {
4365           par                 = 0;
4366         }
4367         this.par[this.npar]   = 10*par + (ch & 0xF);
4368         break;
4369       } else if (this.isEsc == 5 /* ESdeviceattr */) {
4370         switch (ch) {
4371 /*c*/   case 0x63: if (this.par[0] == 0) this.respondSecondaryDA();     break;
4372 /*m*/   case 0x6D: /* (re)set key modifier resource values */           break;
4373 /*n*/   case 0x6E: /* disable key modifier resource values */           break;
4374 /*p*/   case 0x70: /* set pointer mode resource value */                break;
4375         default:                                                        break;
4376         }
4377         this.isEsc            = 0 /* ESnormal */;
4378         break;
4379       } else {
4380         this.isEsc            = 4 /* ESgotpars */;
4381       }
4382       // Fall through
4383     case 4 /* ESgotpars */:
4384       this.isEsc              = 0 /* ESnormal */;
4385       if (this.isQuestionMark) {
4386         switch (ch) {
4387 /*h*/   case 0x68: this.setMode(true);                                  break;
4388 /*l*/   case 0x6C: this.setMode(false);                                 break;
4389 /*c*/   case 0x63: this.setCursorAttr(this.par[2], this.par[1]);        break;
4390         default:                                                        break;
4391         }
4392         this.isQuestionMark   = false;
4393         break;
4394       }
4395       switch (ch) {
4396 /*!*/ case 0x21: this.isEsc   = 12 /* ESbang */;                                 break;
4397 /*>*/ case 0x3E: if (!this.npar) this.isEsc  = 5 /* ESdeviceattr */;            break;
4398 /*G*/ case 0x47:
4399 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY);            break;
4400 /*A*/ case 0x41: this.gotoXY(this.cursorX,
4401                              this.cursorY - (this.par[0] ? this.par[0] : 1));
4402                                                                         break;
4403 /*B*/ case 0x42:
4404 /*e*/ case 0x65: this.gotoXY(this.cursorX,
4405                              this.cursorY + (this.par[0] ? this.par[0] : 1));
4406                                                                         break;
4407 /*C*/ case 0x43:
4408 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4409                              this.cursorY);                             break;
4410 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4411                              this.cursorY);                             break;
4412 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4413                                                                         break;
4414 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4415                                                                         break;
4416 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1);           break;
4417 /*H*/ case 0x48:
4418 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1);        break;
4419 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1);                break;
4420 /*@*/ case 0x40: this.csiAt(this.par[0]);                               break;
4421 /*i*/ case 0x69: this.csii(this.par[0]);                                break;
4422 /*J*/ case 0x4A: this.csiJ(this.par[0]);                                break;
4423 /*K*/ case 0x4B: this.csiK(this.par[0]);                                break;
4424 /*L*/ case 0x4C: this.csiL(this.par[0]);                                break;
4425 /*M*/ case 0x4D: this.csiM(this.par[0]);                                break;
4426 /*m*/ case 0x6D: this.csim();                                           break;
4427 /*P*/ case 0x50: this.csiP(this.par[0]);                                break;
4428 /*X*/ case 0x58: this.csiX(this.par[0]);                                break;
4429 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1);                break;
4430 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1);                break;
4431 /*c*/ case 0x63: if (!this.par[0]) this.respondID();                    break;
4432 /*g*/ case 0x67: if (this.par[0] == 0) {
4433                    this.userTabStop[this.cursorX] = false;
4434                  } else if (this.par[0] == 2 || this.par[0] == 3) {
4435                    this.userTabStop               = [ ];
4436                    for (var i = 0; i < this.terminalWidth; i++) {
4437                      this.userTabStop[i]          = false;
4438                    }
4439                  }
4440                  break;
4441 /*h*/ case 0x68: this.setMode(true);                                    break;
4442 /*l*/ case 0x6C: this.setMode(false);                                   break;
4443 /*n*/ case 0x6E: switch (this.par[0]) {
4444                  case 5: this.statusReport();                           break;
4445                  case 6: this.cursorReport();                           break;
4446                  default:                                               break;
4447                  }
4448                  break;
4449 /*q*/ case 0x71: // LED control not implemented
4450                                                                         break;
4451 /*r*/ case 0x72: var t        = this.par[0] ? this.par[0] : 1;
4452                  var b        = this.par[1] ? this.par[1]
4453                                             : this.terminalHeight;
4454                  if (t < b && b <= this.terminalHeight) {
4455                    this.top   = t - 1;
4456                    this.bottom= b;
4457                    this.gotoXaY(0, 0);
4458                  }
4459                  break;
4460 /*b*/ case 0x62: var c        = this.par[0] ? this.par[0] : 1;
4461                  if (c > this.terminalWidth * this.terminalHeight) {
4462                    c          = this.terminalWidth * this.terminalHeight;
4463                  }
4464                  while (c-- > 0) {
4465                    lineBuf   += this.lastCharacter;
4466                  }
4467                  break;
4468 /*s*/ case 0x73: this.saveCursor();                                     break;
4469 /*u*/ case 0x75: this.restoreCursor();                                  break;
4470 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1);                break;
4471 /*]*/ case 0x5D: this.settermCommand();                                 break;
4472       default:                                                          break;
4473       }
4474       break;
4475     case 12 /* ESbang */:
4476       if (ch == 'p') {
4477         this.reset();
4478       }
4479       this.isEsc              = 0 /* ESnormal */;
4480       break;
4481     case 13 /* ESpercent */:
4482       this.isEsc              = 0 /* ESnormal */;
4483       switch (ch) {
4484 /*@*/ case 0x40: this.utfEnabled = false;                               break;
4485 /*G*/ case 0x47:
4486 /*8*/ case 0x38: this.utfEnabled = true;                                break;
4487       default:                                                          break;
4488       }
4489       break;
4490     case 6 /* ESfunckey */:
4491       this.isEsc              = 0 /* ESnormal */;                               break;
4492     case 7 /* EShash */:
4493       this.isEsc              = 0 /* ESnormal */;
4494 /*8*/ if (ch == 0x38) {
4495         // Screen alignment test not implemented
4496       }
4497       break;
4498     case 8 /* ESsetG0 */:
4499     case 9 /* ESsetG1 */:
4500     case 10 /* ESsetG2 */:
4501     case 11 /* ESsetG3 */:
4502       var g                   = this.isEsc - 8 /* ESsetG0 */;
4503       this.isEsc              = 0 /* ESnormal */;
4504       switch (ch) {
4505 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap;                  break;
4506 /*A*/ case 0x42:
4507 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map;                         break;
4508 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map;                    break;
4509 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap;                   break;
4510       default:                                                          break;
4511       }
4512       if (this.useGMap == g) {
4513         this.translate        = this.GMap[g];
4514       }
4515       break;
4516     case 17 /* EStitle */:
4517       if (ch == 0x07) {
4518         if (this.titleString && this.titleString.charAt(0) == ';') {
4519           this.titleString    = this.titleString.substr(1);
4520           if (this.titleString != '') {
4521             this.titleString += ' - ';
4522           }
4523           this.titleString += 'Shell In A Box'
4524         }
4525         try {
4526           window.document.title = this.titleString;
4527         } catch (e) {
4528         }
4529         this.isEsc            = 0 /* ESnormal */;
4530       } else {
4531         this.titleString     += String.fromCharCode(ch);
4532       }
4533       break;
4534     case 18 /* ESss2 */:
4535     case 19 /* ESss3 */:
4536       if (ch < 256) {
4537           ch                  = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4538                                          [this.toggleMeta ? (ch | 0x80) : ch];
4539         if ((ch & 0xFF00) == 0xF000) {
4540           ch                  = ch & 0xFF;
4541         } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4542           this.isEsc         = 0 /* ESnormal */;                                break;
4543         }
4544       }
4545       this.lastCharacter      = String.fromCharCode(ch);
4546       lineBuf                += this.lastCharacter;
4547       this.isEsc              = 0 /* ESnormal */;                               break;
4548     default:
4549       this.isEsc              = 0 /* ESnormal */;                               break;
4550     }
4551     break;
4552   }
4553   return lineBuf;
4554 };
4555
4556 VT100.prototype.renderString = function(s, showCursor) {
4557   if (this.printing) {
4558     this.sendToPrinter(s);
4559     if (showCursor) {
4560       this.showCursor();
4561     }
4562     return;
4563   }
4564
4565   // We try to minimize the number of DOM operations by coalescing individual
4566   // characters into strings. This is a significant performance improvement.
4567   var incX = s.length;
4568   if (incX > this.terminalWidth - this.cursorX) {
4569     incX   = this.terminalWidth - this.cursorX;
4570     if (incX <= 0) {
4571       return;
4572     }
4573     s      = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4574   }
4575   if (showCursor) {
4576     // Minimize the number of calls to putString(), by avoiding a direct
4577     // call to this.showCursor()
4578     this.cursor.style.visibility = '';
4579   }
4580   this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4581 };
4582
4583 VT100.prototype.vt100 = function(s) {
4584   this.cursorNeedsShowing = this.hideCursor();
4585   this.respondString      = '';
4586   var lineBuf             = '';
4587   for (var i = 0; i < s.length; i++) {
4588     var ch = s.charCodeAt(i);
4589     if (this.utfEnabled) {
4590       // Decode UTF8 encoded character
4591       if (ch > 0x7F) {
4592         if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4593           this.utfChar    = (this.utfChar << 6) | (ch & 0x3F);
4594           if (--this.utfCount <= 0) {
4595             if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4596               ch = 0xFFFD;
4597             } else {
4598               ch          = this.utfChar;
4599             }
4600           } else {
4601             continue;
4602           }
4603         } else {
4604           if ((ch & 0xE0) == 0xC0) {
4605             this.utfCount = 1;
4606             this.utfChar  = ch & 0x1F;
4607           } else if ((ch & 0xF0) == 0xE0) {
4608             this.utfCount = 2;
4609             this.utfChar  = ch & 0x0F;
4610           } else if ((ch & 0xF8) == 0xF0) {
4611             this.utfCount = 3;
4612             this.utfChar  = ch & 0x07;
4613           } else if ((ch & 0xFC) == 0xF8) {
4614             this.utfCount = 4;
4615             this.utfChar  = ch & 0x03;
4616           } else if ((ch & 0xFE) == 0xFC) {
4617             this.utfCount = 5;
4618             this.utfChar  = ch & 0x01;
4619           } else {
4620             this.utfCount = 0;
4621           }
4622           continue;
4623         }
4624       } else {
4625         this.utfCount     = 0;
4626       }
4627     }
4628     var isNormalCharacter =
4629       (ch >= 32 && ch <= 127 || ch >= 160 ||
4630        this.utfEnabled && ch >= 128 ||
4631        !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4632       (ch != 0x7F || this.dispCtrl);
4633
4634     if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4635       if (ch < 256) {
4636         ch                = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4637       }
4638       if ((ch & 0xFF00) == 0xF000) {
4639         ch                = ch & 0xFF;
4640       } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4641         continue;
4642       }
4643       if (!this.printing) {
4644         if (this.needWrap || this.insertMode) {
4645           if (lineBuf) {
4646             this.renderString(lineBuf);
4647             lineBuf       = '';
4648           }
4649         }
4650         if (this.needWrap) {
4651           this.cr(); this.lf();
4652         }
4653         if (this.insertMode) {
4654           this.scrollRegion(this.cursorX, this.cursorY,
4655                             this.terminalWidth - this.cursorX - 1, 1,
4656                             1, 0, this.color, this.style);
4657         }
4658       }
4659       this.lastCharacter  = String.fromCharCode(ch);
4660       lineBuf            += this.lastCharacter;
4661       if (!this.printing &&
4662           this.cursorX + lineBuf.length >= this.terminalWidth) {
4663         this.needWrap     = this.autoWrapMode;
4664       }
4665     } else {
4666       if (lineBuf) {
4667         this.renderString(lineBuf);
4668         lineBuf           = '';
4669       }
4670       var expand          = this.doControl(ch);
4671       if (expand.length) {
4672         var r             = this.respondString;
4673         this.respondString= r + this.vt100(expand);
4674       }
4675     }
4676   }
4677   if (lineBuf) {
4678     this.renderString(lineBuf, this.cursorNeedsShowing);
4679   } else if (this.cursorNeedsShowing) {
4680     this.showCursor();
4681   }
4682   return this.respondString;
4683 };
4684
4685 VT100.prototype.Latin1Map = [
4686 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4687 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4688 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4689 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4690 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4691 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4692 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4693 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4694 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4695 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4696 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4697 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4698 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4699 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4700 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4701 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4702 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4703 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4704 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4705 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4706 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4707 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4708 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4709 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4710 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4711 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4712 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4713 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4714 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4715 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4716 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4717 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4718 ];
4719
4720 VT100.prototype.VT100GraphicsMap = [
4721 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4722 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4723 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4724 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4725 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4726 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4727 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4728 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4729 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4730 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4731 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4732 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4733 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4734 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4735 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4736 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4737 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4738 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4739 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4740 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4741 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4742 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4743 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4744 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4745 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4746 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4747 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4748 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4749 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4750 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4751 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4752 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4753 ];
4754
4755 VT100.prototype.CodePage437Map = [
4756 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4757 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4758 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4759 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4760 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4761 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4762 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4763 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4764 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4765 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4766 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4767 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4768 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4769 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4770 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4771 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4772 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4773 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4774 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4775 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4776 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4777 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4778 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4779 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4780 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4781 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4782 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4783 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4784 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4785 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4786 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4787 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4788 ];
4789
4790 VT100.prototype.DirectToFontMap = [
4791 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4792 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4793 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4794 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4795 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4796 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4797 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4798 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4799 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4800 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4801 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4802 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4803 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4804 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4805 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4806 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4807 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4808 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4809 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4810 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4811 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4812 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4813 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4814 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4815 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4816 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4817 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4818 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4819 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4820 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4821 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4822 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4823 ];
4824
4825 VT100.prototype.ctrlAction = [
4826   true,  false, false, false, false, false, false, true,
4827   true,  true,  true,  true,  true,  true,  true,  true,
4828   false, false, false, false, false, false, false, false,
4829   true,  false, true,  true,  false, false, false, false
4830 ];
4831
4832 VT100.prototype.ctrlAlways = [
4833   true,  false, false, false, false, false, false, false,
4834   true,  false, true,  false, true,  true,  true,  true,
4835   false, false, false, false, false, false, false, false,
4836   false, false, false, true,  false, false, false, false
4837 ];