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