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