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