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