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