]> andersk Git - test.git/blob - shellinabox/vt100.jspp
Bold and underline SGR attributes now utilize CSS.
[test.git] / shellinabox / vt100.jspp
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 EStitle        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.titleString        = '';
191   this.internalClipboard  = undefined;
192   this.reset(true);
193 }
194
195 VT100.prototype.reset = function(clearHistory) {
196   this.isEsc                                         = 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                                          = 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, MOUSE_DOWN));
1047   this.addListener(this.scrollable,'mouseup',  mouseEvent(this, MOUSE_UP));
1048   this.addListener(this.scrollable,'click',    mouseEvent(this, 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 == MOUSE_UP || type == 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 != 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 != MOUSE_DOWN || !event.shiftKey)) {
1371     if (inside || type != 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 != 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 == 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   this.animateCursor('bright');
2403 };
2404
2405 VT100.prototype.about = function() {
2406   alert("VT100 Terminal Emulator " + VERSION +
2407         "\nCopyright 2008-2010 by Markus Gutschke\n" +
2408         "For more information check http://shellinabox.com");
2409 };
2410
2411 VT100.prototype.hideContextMenu = function() {
2412   this.menu.style.visibility = 'hidden';
2413   this.menu.style.top        = '-100px';
2414   this.menu.style.left       = '-100px';
2415   this.menu.style.width      = '0px';
2416   this.menu.style.height     = '0px';
2417 };
2418
2419 VT100.prototype.extendContextMenu = function(entries, actions) {
2420 };
2421
2422 VT100.prototype.showContextMenu = function(x, y) {
2423   this.menu.innerHTML         =
2424     '<table class="popup" ' +
2425            'cellpadding="0" cellspacing="0">' +
2426       '<tr><td>' +
2427         '<ul id="menuentries">' +
2428           '<li id="beginclipboard">Copy</li>' +
2429           '<li id="endclipboard">Paste</li>' +
2430           '<hr />' +
2431           '<li id="reset">Reset</li>' +
2432           '<hr />' +
2433           '<li id="beginconfig">' +
2434              (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2435              'Unicode</li>' +
2436           '<li>' +
2437              (this.visualBell ? '<img src="enabled.gif" />' : '') +
2438              'Visual Bell</li>'+
2439           '<li>' +
2440              (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2441              'Onscreen Keyboard</li>' +
2442           '<li id="endconfig">' +
2443              (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2444              'Blinking Cursor</li>'+
2445           (this.usercss.firstChild ?
2446            '<hr id="beginusercss" />' +
2447            this.usercss.innerHTML +
2448            '<hr id="endusercss" />' :
2449            '<hr />') +
2450           '<li id="about">About...</li>' +
2451         '</ul>' +
2452       '</td></tr>' +
2453     '</table>';
2454
2455   var popup                   = this.menu.firstChild;
2456   var menuentries             = this.getChildById(popup, 'menuentries');
2457
2458   // Determine menu entries that should be disabled
2459   this.lastSelection          = this.selection();
2460   if (!this.lastSelection.length) {
2461     menuentries.firstChild.className
2462                               = 'disabled';
2463   }
2464   var p                       = this.pasteFnc();
2465   if (!p) {
2466     menuentries.childNodes[1].className
2467                               = 'disabled';
2468   }
2469
2470   // Actions for default items
2471   var actions                 = [ this.copyLast, p, this.reset,
2472                                   this.toggleUTF, this.toggleBell,
2473                                   this.toggleSoftKeyboard,
2474                                   this.toggleCursorBlinking ];
2475
2476   // Actions for user CSS styles (if any)
2477   for (var i = 0; i < this.usercssActions.length; ++i) {
2478     actions[actions.length]   = this.usercssActions[i];
2479   }
2480   actions[actions.length]     = this.about;
2481
2482   // Allow subclasses to dynamically add entries to the context menu
2483   this.extendContextMenu(menuentries, actions);
2484
2485   // Hook up event listeners
2486   for (var node = menuentries.firstChild, i = 0; node;
2487        node = node.nextSibling) {
2488     if (node.tagName == 'LI') {
2489       if (node.className != 'disabled') {
2490         this.addListener(node, 'mouseover',
2491                          function(vt100, node) {
2492                            return function() {
2493                              node.className = 'hover';
2494                            }
2495                          }(this, node));
2496         this.addListener(node, 'mouseout',
2497                          function(vt100, node) {
2498                            return function() {
2499                              node.className = '';
2500                            }
2501                          }(this, node));
2502         this.addListener(node, 'mousedown',
2503                          function(vt100, action) {
2504                            return function(event) {
2505                              vt100.hideContextMenu();
2506                              action.call(vt100);
2507                              vt100.storeUserSettings();
2508                              return vt100.cancelEvent(event || window.event);
2509                            }
2510                          }(this, actions[i]));
2511         this.addListener(node, 'mouseup',
2512                          function(vt100) {
2513                            return function(event) {
2514                              return vt100.cancelEvent(event || window.event);
2515                            }
2516                          }(this));
2517         this.addListener(node, 'mouseclick',
2518                          function(vt100) {
2519                            return function(event) {
2520                              return vt100.cancelEvent(event || window.event);
2521                            }
2522                          }());
2523       }
2524       i++;
2525     }
2526   }
2527
2528   // Position menu next to the mouse pointer
2529   this.menu.style.left        = '0px';
2530   this.menu.style.top         = '0px';
2531   this.menu.style.width       =  this.container.offsetWidth  + 'px';
2532   this.menu.style.height      =  this.container.offsetHeight + 'px';
2533   popup.style.left            = '0px';
2534   popup.style.top             = '0px';
2535   
2536   var margin                  = 2;
2537   if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2538     x              = this.container.offsetWidth-popup.clientWidth - margin - 1;
2539   }
2540   if (x < margin) {
2541     x                         = margin;
2542   }
2543   if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2544     y            = this.container.offsetHeight-popup.clientHeight - margin - 1;
2545   }
2546   if (y < margin) {
2547     y                         = margin;
2548   }
2549   popup.style.left            = x + 'px';
2550   popup.style.top             = y + 'px';
2551
2552   // Block all other interactions with the terminal emulator
2553   this.addListener(this.menu, 'click', function(vt100) {
2554                                          return function() {
2555                                            vt100.hideContextMenu();
2556                                          }
2557                                        }(this));
2558
2559   // Show the menu
2560   this.menu.style.visibility  = '';
2561 };
2562
2563 VT100.prototype.keysPressed = function(ch) {
2564   for (var i = 0; i < ch.length; i++) {
2565     var c = ch.charCodeAt(i);
2566     this.vt100(c >= 7 && c <= 15 ||
2567                c == 24 || c == 26 || c == 27 || c >= 32
2568                ? String.fromCharCode(c) : '<' + c + '>');
2569   }
2570 };
2571
2572 VT100.prototype.applyModifiers = function(ch, event) {
2573   if (ch) {
2574     if (event.ctrlKey) {
2575       if (ch >= 32 && ch <= 127) {
2576         // For historic reasons, some control characters are treated specially
2577         switch (ch) {
2578         case /* 3 */ 51: ch  =  27; break;
2579         case /* 4 */ 52: ch  =  28; break;
2580         case /* 5 */ 53: ch  =  29; break;
2581         case /* 6 */ 54: ch  =  30; break;
2582         case /* 7 */ 55: ch  =  31; break;
2583         case /* 8 */ 56: ch  = 127; break;
2584         case /* ? */ 63: ch  = 127; break;
2585         default:         ch &=  31; break;
2586         }
2587       }
2588     }
2589     return String.fromCharCode(ch);
2590   } else {
2591     return undefined;
2592   }
2593 };
2594
2595 VT100.prototype.handleKey = function(event) {
2596   // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2597   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2598   //             event.metaKey ? ', ' +
2599   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2600   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2601   //            '\r\n');
2602   var ch, key;
2603   if (typeof event.charCode != 'undefined') {
2604     // non-IE keypress events have a translated charCode value. Also, our
2605     // fake events generated when receiving keydown events include this data
2606     // on all browsers.
2607     ch                                = event.charCode;
2608     key                               = event.keyCode;
2609   } else {
2610     // When sending a keypress event, IE includes the translated character
2611     // code in the keyCode field.
2612     ch                                = event.keyCode;
2613     key                               = undefined;
2614   }
2615
2616   // Apply modifier keys (ctrl and shift)
2617   if (ch) {
2618     key                               = undefined;
2619   }
2620   ch                                  = this.applyModifiers(ch, event);
2621
2622   // By this point, "ch" is either defined and contains the character code, or
2623   // it is undefined and "key" defines the code of a function key 
2624   if (ch != undefined) {
2625     this.scrollable.scrollTop         = this.numScrollbackLines *
2626                                         this.cursorHeight + 1;
2627   } else {
2628     if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2629       // Many programs have difficulties dealing with parametrized escape
2630       // sequences for function keys. Thus, if ALT is the only modifier
2631       // key, return Emacs-style keycodes for commonly used keys.
2632       switch (key) {
2633       case  33: /* Page Up      */ ch = '\u001B<';                      break;
2634       case  34: /* Page Down    */ ch = '\u001B>';                      break;
2635       case  37: /* Left         */ ch = '\u001Bb';                      break;
2636       case  38: /* Up           */ ch = '\u001Bp';                      break;
2637       case  39: /* Right        */ ch = '\u001Bf';                      break;
2638       case  40: /* Down         */ ch = '\u001Bn';                      break;
2639       case  46: /* Delete       */ ch = '\u001Bd';                      break;
2640       default:                                                          break;
2641       }
2642     } else if (event.shiftKey && !event.ctrlKey &&
2643                !event.altKey && !event.metaKey) {
2644       switch (key) {
2645       case  33: /* Page Up      */ this.scrollBack();                   return;
2646       case  34: /* Page Down    */ this.scrollFore();                   return;
2647       default:                                                          break;
2648       }
2649     }
2650     if (ch == undefined) {
2651       switch (key) {
2652       case   8: /* Backspace    */ ch = '\u007f';                       break;
2653       case   9: /* Tab          */ ch = '\u0009';                       break;
2654       case  10: /* Return       */ ch = '\u000A';                       break;
2655       case  13: /* Enter        */ ch = this.crLfMode ?
2656                                         '\r\n' : '\r';                  break;
2657       case  16: /* Shift        */                                      return;
2658       case  17: /* Ctrl         */                                      return;
2659       case  18: /* Alt          */                                      return;
2660       case  19: /* Break        */                                      return;
2661       case  20: /* Caps Lock    */                                      return;
2662       case  27: /* Escape       */ ch = '\u001B';                       break;
2663       case  33: /* Page Up      */ ch = '\u001B[5~';                    break;
2664       case  34: /* Page Down    */ ch = '\u001B[6~';                    break;
2665       case  35: /* End          */ ch = '\u001BOF';                     break;
2666       case  36: /* Home         */ ch = '\u001BOH';                     break;
2667       case  37: /* Left         */ ch = this.cursorKeyMode ?
2668                              '\u001BOD' : '\u001B[D';                   break;
2669       case  38: /* Up           */ ch = this.cursorKeyMode ?
2670                              '\u001BOA' : '\u001B[A';                   break;
2671       case  39: /* Right        */ ch = this.cursorKeyMode ?
2672                              '\u001BOC' : '\u001B[C';                   break;
2673       case  40: /* Down         */ ch = this.cursorKeyMode ?
2674                              '\u001BOB' : '\u001B[B';                   break;
2675       case  45: /* Insert       */ ch = '\u001B[2~';                    break;
2676       case  46: /* Delete       */ ch = '\u001B[3~';                    break;
2677       case  91: /* Left Window  */                                      return;
2678       case  92: /* Right Window */                                      return;
2679       case  93: /* Select       */                                      return;
2680       case  96: /* 0            */ ch = this.applyModifiers(48, event); break;
2681       case  97: /* 1            */ ch = this.applyModifiers(49, event); break;
2682       case  98: /* 2            */ ch = this.applyModifiers(50, event); break;
2683       case  99: /* 3            */ ch = this.applyModifiers(51, event); break;
2684       case 100: /* 4            */ ch = this.applyModifiers(52, event); break;
2685       case 101: /* 5            */ ch = this.applyModifiers(53, event); break;
2686       case 102: /* 6            */ ch = this.applyModifiers(54, event); break;
2687       case 103: /* 7            */ ch = this.applyModifiers(55, event); break;
2688       case 104: /* 8            */ ch = this.applyModifiers(56, event); break;
2689       case 105: /* 9            */ ch = this.applyModifiers(58, event); break;
2690       case 106: /* *            */ ch = this.applyModifiers(42, event); break;
2691       case 107: /* +            */ ch = this.applyModifiers(43, event); break;
2692       case 109: /* -            */ ch = this.applyModifiers(45, event); break;
2693       case 110: /* .            */ ch = this.applyModifiers(46, event); break;
2694       case 111: /* /            */ ch = this.applyModifiers(47, event); break;
2695       case 112: /* F1           */ ch = '\u001BOP';                     break;
2696       case 113: /* F2           */ ch = '\u001BOQ';                     break;
2697       case 114: /* F3           */ ch = '\u001BOR';                     break;
2698       case 115: /* F4           */ ch = '\u001BOS';                     break;
2699       case 116: /* F5           */ ch = '\u001B[15~';                   break;
2700       case 117: /* F6           */ ch = '\u001B[17~';                   break;
2701       case 118: /* F7           */ ch = '\u001B[18~';                   break;
2702       case 119: /* F8           */ ch = '\u001B[19~';                   break;
2703       case 120: /* F9           */ ch = '\u001B[20~';                   break;
2704       case 121: /* F10          */ ch = '\u001B[21~';                   break;
2705       case 122: /* F11          */ ch = '\u001B[23~';                   break;
2706       case 123: /* F12          */ ch = '\u001B[24~';                   break;
2707       case 144: /* Num Lock     */                                      return;
2708       case 145: /* Scroll Lock  */                                      return;
2709       case 186: /* ;            */ ch = this.applyModifiers(59, event); break;
2710       case 187: /* =            */ ch = this.applyModifiers(61, event); break;
2711       case 188: /* ,            */ ch = this.applyModifiers(44, event); break;
2712       case 189: /* -            */ ch = this.applyModifiers(45, event); break;
2713       case 190: /* .            */ ch = this.applyModifiers(46, event); break;
2714       case 191: /* /            */ ch = this.applyModifiers(47, event); break;
2715       case 192: /* `            */ ch = this.applyModifiers(96, event); break;
2716       case 219: /* [            */ ch = this.applyModifiers(91, event); break;
2717       case 220: /* \            */ ch = this.applyModifiers(92, event); break;
2718       case 221: /* ]            */ ch = this.applyModifiers(93, event); break;
2719       case 222: /* '            */ ch = this.applyModifiers(39, event); break;
2720       default:                                                          return;
2721       }
2722       this.scrollable.scrollTop       = this.numScrollbackLines *
2723                                         this.cursorHeight + 1;
2724     }
2725   }
2726
2727   // "ch" now contains the sequence of keycodes to send. But we might still
2728   // have to apply the effects of modifier keys.
2729   if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2730     var start, digit, part1, part2;
2731     if ((start = ch.substr(0, 2)) == '\u001B[') {
2732       for (part1 = start;
2733            part1.length < ch.length &&
2734              (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2735         part1                         = ch.substr(0, part1.length + 1);
2736       }
2737       part2                           = ch.substr(part1.length);
2738       if (part1.length > 2) {
2739         part1                        += ';';
2740       }
2741     } else if (start == '\u001BO') {
2742       part1                           = start;
2743       part2                           = ch.substr(2);
2744     }
2745     if (part1 != undefined) {
2746       ch                              = part1                                 +
2747                                        ((event.shiftKey             ? 1 : 0)  +
2748                                         (event.altKey|event.metaKey ? 2 : 0)  +
2749                                         (event.ctrlKey              ? 4 : 0)) +
2750                                         part2;
2751     } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2752       ch                              = '\u001B' + ch;
2753     }
2754   }
2755
2756   if (this.menu.style.visibility == 'hidden') {
2757     // this.vt100('R: c=');
2758     // for (var i = 0; i < ch.length; i++)
2759     //   this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2760     // this.vt100('\r\n');
2761     this.keysPressed(ch);
2762   }
2763 };
2764
2765 VT100.prototype.inspect = function(o, d) {
2766   if (d == undefined) {
2767     d       = 0;
2768   }
2769   var rc    = '';
2770   if (typeof o == 'object' && ++d < 2) {
2771     rc      = '[\r\n';
2772     for (i in o) {
2773       rc   += this.spaces(d * 2) + i + ' -> ';
2774       try {
2775         rc += this.inspect(o[i], d);
2776       } catch (e) {
2777         rc += '?' + '?' + '?\r\n';
2778       }
2779     }
2780     rc     += ']\r\n';
2781   } else {
2782     rc     += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2783   }
2784   return rc;
2785 };
2786
2787 VT100.prototype.checkComposedKeys = function(event) {
2788   // Composed keys (at least on Linux) do not generate normal events.
2789   // Instead, they get entered into the text field. We normally catch
2790   // this on the next keyup event.
2791   var s              = this.input.value;
2792   if (s.length) {
2793     this.input.value = '';
2794     if (this.menu.style.visibility == 'hidden') {
2795       this.keysPressed(s);
2796     }
2797   }
2798 };
2799
2800 VT100.prototype.fixEvent = function(event) {
2801   // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2802   // is used as a second-level selector, clear the modifier bits before
2803   // handling the event.
2804   if (event.ctrlKey && event.altKey) {
2805     var fake                = [ ];
2806     fake.charCode           = event.charCode;
2807     fake.keyCode            = event.keyCode;
2808     fake.ctrlKey            = false;
2809     fake.shiftKey           = event.shiftKey;
2810     fake.altKey             = false;
2811     fake.metaKey            = event.metaKey;
2812     return fake;
2813   }
2814
2815   // Some browsers fail to translate keys, if both shift and alt/meta is
2816   // pressed at the same time. We try to translate those cases, but that
2817   // only works for US keyboard layouts.
2818   if (event.shiftKey) {
2819     var u                   = undefined;
2820     var s                   = undefined;
2821     switch (this.lastNormalKeyDownEvent.keyCode) {
2822     case  39: /* ' -> " */ u = 39; s =  34; break;
2823     case  44: /* , -> < */ u = 44; s =  60; break;
2824     case  45: /* - -> _ */ u = 45; s =  95; break;
2825     case  46: /* . -> > */ u = 46; s =  62; break;
2826     case  47: /* / -> ? */ u = 47; s =  63; break;
2827
2828     case  48: /* 0 -> ) */ u = 48; s =  41; break;
2829     case  49: /* 1 -> ! */ u = 49; s =  33; break;
2830     case  50: /* 2 -> @ */ u = 50; s =  64; break;
2831     case  51: /* 3 -> # */ u = 51; s =  35; break;
2832     case  52: /* 4 -> $ */ u = 52; s =  36; break;
2833     case  53: /* 5 -> % */ u = 53; s =  37; break;
2834     case  54: /* 6 -> ^ */ u = 54; s =  94; break;
2835     case  55: /* 7 -> & */ u = 55; s =  38; break;
2836     case  56: /* 8 -> * */ u = 56; s =  42; break;
2837     case  57: /* 9 -> ( */ u = 57; s =  40; break;
2838
2839     case  59: /* ; -> : */ u = 59; s =  58; break;
2840     case  61: /* = -> + */ u = 61; s =  43; break;
2841     case  91: /* [ -> { */ u = 91; s = 123; break;
2842     case  92: /* \ -> | */ u = 92; s = 124; break;
2843     case  93: /* ] -> } */ u = 93; s = 125; break; 
2844     case  96: /* ` -> ~ */ u = 96; s = 126; break;
2845
2846     case 109: /* - -> _ */ u = 45; s =  95; break;
2847     case 111: /* / -> ? */ u = 47; s =  63; break;
2848
2849     case 186: /* ; -> : */ u = 59; s =  58; break;
2850     case 187: /* = -> + */ u = 61; s =  43; break;
2851     case 188: /* , -> < */ u = 44; s =  60; break;
2852     case 189: /* - -> _ */ u = 45; s =  95; break;
2853     case 190: /* . -> > */ u = 46; s =  62; break;
2854     case 191: /* / -> ? */ u = 47; s =  63; break;
2855     case 192: /* ` -> ~ */ u = 96; s = 126; break;
2856     case 219: /* [ -> { */ u = 91; s = 123; break;
2857     case 220: /* \ -> | */ u = 92; s = 124; break;
2858     case 221: /* ] -> } */ u = 93; s = 125; break; 
2859     case 222: /* ' -> " */ u = 39; s =  34; break;
2860     default:                                break;
2861     }
2862     if (s && (event.charCode == u || event.charCode == 0)) {
2863       var fake              = [ ];
2864       fake.charCode         = s;
2865       fake.keyCode          = event.keyCode;
2866       fake.ctrlKey          = event.ctrlKey;
2867       fake.shiftKey         = event.shiftKey;
2868       fake.altKey           = event.altKey;
2869       fake.metaKey          = event.metaKey;
2870       return fake;
2871     }
2872   }
2873   return event;
2874 };
2875
2876 VT100.prototype.keyDown = function(event) {
2877   // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2878   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2879   //             event.metaKey ? ', ' +
2880   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2881   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2882   //            '\r\n');
2883
2884   // Keep the cursor lit when there is user activity.
2885   this.animateCursor('bright');
2886
2887   this.checkComposedKeys(event);
2888   this.lastKeyPressedEvent      = undefined;
2889   this.lastKeyDownEvent         = undefined;
2890   this.lastNormalKeyDownEvent   = event;
2891
2892   var asciiKey                  =
2893     event.keyCode ==  32                         ||
2894     event.keyCode >=  48 && event.keyCode <=  57 ||
2895     event.keyCode >=  65 && event.keyCode <=  90;
2896   var alphNumKey                =
2897     asciiKey                                     ||
2898     event.keyCode >=  96 && event.keyCode <= 105 ||
2899     event.keyCode == 226;
2900   var normalKey                 =
2901     alphNumKey                                   ||
2902     event.keyCode ==  59 || event.keyCode ==  61 ||
2903     event.keyCode == 106 || event.keyCode == 107 ||
2904     event.keyCode >= 109 && event.keyCode <= 111 ||
2905     event.keyCode >= 186 && event.keyCode <= 192 ||
2906     event.keyCode >= 219 && event.keyCode <= 223 ||
2907     event.keyCode == 252;
2908   try {
2909     if (navigator.appName == 'Konqueror') {
2910       normalKey                |= event.keyCode < 128;
2911     }
2912   } catch (e) {
2913   }
2914
2915   // We normally prefer to look at keypress events, as they perform the
2916   // translation from keyCode to charCode. This is important, as the
2917   // translation is locale-dependent.
2918   // But for some keys, we must intercept them during the keydown event,
2919   // as they would otherwise get interpreted by the browser.
2920   // Even, when doing all of this, there are some keys that we can never
2921   // intercept. This applies to some of the menu navigation keys in IE.
2922   // In fact, we see them, but we cannot stop IE from seeing them, too.
2923   if ((event.charCode || event.keyCode) &&
2924       ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2925         !event.shiftKey &&
2926         // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2927         // interpret this sequence ourselves, as some keyboard layouts use
2928         // it for second-level layouts.
2929         !(event.ctrlKey && event.altKey)) ||
2930        this.catchModifiersEarly && normalKey && !alphNumKey &&
2931        (event.ctrlKey || event.altKey || event.metaKey) ||
2932        !normalKey)) {
2933     this.lastKeyDownEvent       = event;
2934     var fake                    = [ ];
2935     fake.ctrlKey                = event.ctrlKey;
2936     fake.shiftKey               = event.shiftKey;
2937     fake.altKey                 = event.altKey;
2938     fake.metaKey                = event.metaKey;
2939     if (asciiKey) {
2940       fake.charCode             = event.keyCode;
2941       fake.keyCode              = 0;
2942     } else {
2943       fake.charCode             = 0;
2944       fake.keyCode              = event.keyCode;
2945       if (!alphNumKey && event.shiftKey) {
2946         fake                    = this.fixEvent(fake);
2947       }
2948     }
2949
2950     this.handleKey(fake);
2951     this.lastNormalKeyDownEvent = undefined;
2952
2953     try {
2954       // For non-IE browsers
2955       event.stopPropagation();
2956       event.preventDefault();
2957     } catch (e) {
2958     }
2959     try {
2960       // For IE
2961       event.cancelBubble = true;
2962       event.returnValue  = false;
2963       event.keyCode      = 0;
2964     } catch (e) {
2965     }
2966
2967     return false;
2968   }
2969   return true;
2970 };
2971
2972 VT100.prototype.keyPressed = function(event) {
2973   // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2974   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2975   //             event.metaKey ? ', ' +
2976   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2977   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2978   //            '\r\n');
2979
2980   // Keep the cursor lit when there is user activity.
2981   this.animateCursor('bright');
2982
2983   if (this.lastKeyDownEvent) {
2984     // If we already processed the key on keydown, do not process it
2985     // again here. Ideally, the browser should not even have generated a
2986     // keypress event in this case. But that does not appear to always work.
2987     this.lastKeyDownEvent     = undefined;
2988   } else {
2989     this.handleKey(event.altKey || event.metaKey
2990                    ? this.fixEvent(event) : event);
2991   }
2992
2993   try {
2994     // For non-IE browsers
2995     event.preventDefault();
2996   } catch (e) {
2997   }
2998
2999   try {
3000     // For IE
3001     event.cancelBubble = true;
3002     event.returnValue  = false;
3003     event.keyCode      = 0;
3004   } catch (e) {
3005   }
3006
3007   this.lastNormalKeyDownEvent = undefined;
3008   this.lastKeyPressedEvent    = event;
3009   return false;
3010 };
3011
3012 VT100.prototype.keyUp = function(event) {
3013   // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3014   //            (event.shiftKey || event.ctrlKey || event.altKey ||
3015   //             event.metaKey ? ', ' +
3016   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3017   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3018   //            '\r\n');
3019
3020   // Keep the cursor lit when there is user activity.
3021   this.animateCursor('bright');
3022
3023   if (this.lastKeyPressedEvent) {
3024     // The compose key on Linux occasionally confuses the browser and keeps
3025     // inserting bogus characters into the input field, even if just a regular
3026     // key has been pressed. Detect this case and drop the bogus characters.
3027     (event.target ||
3028      event.srcElement).value      = '';
3029   } else {
3030     // This is usually were we notice that a key has been composed and
3031     // thus failed to generate normal events.
3032     this.checkComposedKeys(event);
3033
3034     // Some browsers don't report keypress events if ctrl or alt is pressed
3035     // for non-alphanumerical keys. Patch things up for now, but in the
3036     // future we will catch these keys earlier (in the keydown handler).
3037     if (this.lastNormalKeyDownEvent) {
3038       // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3039       this.catchModifiersEarly    = true;
3040       var asciiKey                =
3041         event.keyCode ==  32                         ||
3042         event.keyCode >=  48 && event.keyCode <=  57 ||
3043         event.keyCode >=  65 && event.keyCode <=  90;
3044       var alphNumKey              =
3045         asciiKey                                     ||
3046         event.keyCode >=  96 && event.keyCode <= 105;
3047       var normalKey               =
3048         alphNumKey                                   ||
3049         event.keyCode ==  59 || event.keyCode ==  61 ||
3050         event.keyCode == 106 || event.keyCode == 107 ||
3051         event.keyCode >= 109 && event.keyCode <= 111 ||
3052         event.keyCode >= 186 && event.keyCode <= 192 ||
3053         event.keyCode >= 219 && event.keyCode <= 223 ||
3054         event.keyCode == 252;
3055       var fake                    = [ ];
3056       fake.ctrlKey                = event.ctrlKey;
3057       fake.shiftKey               = event.shiftKey;
3058       fake.altKey                 = event.altKey;
3059       fake.metaKey                = event.metaKey;
3060       if (asciiKey) {
3061         fake.charCode             = event.keyCode;
3062         fake.keyCode              = 0;
3063       } else {
3064         fake.charCode             = 0;
3065         fake.keyCode              = event.keyCode;
3066         if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3067           fake                    = this.fixEvent(fake);
3068         }
3069       }
3070       this.lastNormalKeyDownEvent = undefined;
3071       this.handleKey(fake);
3072     }
3073   }
3074
3075   try {
3076     // For IE
3077     event.cancelBubble            = true;
3078     event.returnValue             = false;
3079     event.keyCode                 = 0;
3080   } catch (e) {
3081   }
3082
3083   this.lastKeyDownEvent           = undefined;
3084   this.lastKeyPressedEvent        = undefined;
3085   return false;
3086 };
3087
3088 VT100.prototype.animateCursor = function(state) {
3089   if (state != undefined) {
3090     this.cursor.className = state;
3091
3092     // Reset the blink timer when a state is explicitly defined.
3093     if (this.cursorInterval) {
3094       clearInterval(this.cursorInterval);
3095       this.cursorInterval = undefined;
3096     }
3097   } else {
3098     if (this.cursor.className != 'inactive') {
3099       if (this.blinkingCursor && this.cursor.className == 'bright') {
3100         this.cursor.className = 'dim';
3101       } else {
3102         this.cursor.className = 'bright';
3103       }
3104     }
3105   }
3106
3107   if (!this.cursorInterval) {
3108     this.cursorInterval       = setInterval(
3109       function(vt100) {
3110         return function() {
3111           vt100.animateCursor();
3112
3113           // Use this opportunity to check whether the user entered a composed
3114           // key, or whether somebody pasted text into the textfield.
3115           vt100.checkComposedKeys();
3116         }
3117       }(this), 500);
3118   }
3119 };
3120
3121 VT100.prototype.blurCursor = function() {
3122   this.animateCursor('inactive');
3123 };
3124
3125 VT100.prototype.focusCursor = function() {
3126   this.animateCursor('bright');
3127 };
3128
3129 VT100.prototype.flashScreen = function() {
3130   this.isInverted       = !this.isInverted;
3131   this.refreshInvertedState();
3132   this.isInverted       = !this.isInverted;
3133   setTimeout(function(vt100) {
3134                return function() {
3135                  vt100.refreshInvertedState();
3136                };
3137              }(this), 100);
3138 };
3139
3140 VT100.prototype.beep = function() {
3141   if (this.visualBell) {
3142     this.flashScreen();
3143   } else {
3144     try {
3145       this.beeper.Play();
3146     } catch (e) {
3147       try {
3148         this.beeper.src = 'beep.wav';
3149       } catch (e) {
3150       }
3151     }
3152   }
3153 };
3154
3155 VT100.prototype.bs = function() {
3156   if (this.cursorX > 0) {
3157     this.gotoXY(this.cursorX - 1, this.cursorY);
3158     this.needWrap = false;
3159   }
3160 };
3161
3162 VT100.prototype.ht = function(count) {
3163   if (count == undefined) {
3164     count        = 1;
3165   }
3166   var cx         = this.cursorX;
3167   while (count-- > 0) {
3168     while (cx++ < this.terminalWidth) {
3169       var tabState = this.userTabStop[cx];
3170       if (tabState == false) {
3171         // Explicitly cleared tab stop
3172         continue;
3173       } else if (tabState) {
3174         // Explicitly set tab stop
3175         break;
3176       } else {
3177         // Default tab stop at each eighth column
3178         if (cx % 8 == 0) {
3179           break;
3180         }
3181       }
3182     }
3183   }
3184   if (cx > this.terminalWidth - 1) {
3185     cx           = this.terminalWidth - 1;
3186   }
3187   if (cx != this.cursorX) {
3188     this.gotoXY(cx, this.cursorY);
3189   }
3190 };
3191
3192 VT100.prototype.rt = function(count) {
3193   if (count == undefined) {
3194     count          = 1 ;
3195   }
3196   var cx           = this.cursorX;
3197   while (count-- > 0) {
3198     while (cx-- > 0) {
3199       var tabState = this.userTabStop[cx];
3200       if (tabState == false) {
3201         // Explicitly cleared tab stop
3202         continue;
3203       } else if (tabState) {
3204         // Explicitly set tab stop
3205         break;
3206       } else {
3207         // Default tab stop at each eighth column
3208         if (cx % 8 == 0) {
3209           break;
3210         }
3211       }
3212     }
3213   }
3214   if (cx < 0) {
3215     cx             = 0;
3216   }
3217   if (cx != this.cursorX) {
3218     this.gotoXY(cx, this.cursorY);
3219   }
3220 };
3221
3222 VT100.prototype.cr = function() {
3223   this.gotoXY(0, this.cursorY);
3224   this.needWrap = false;
3225 };
3226
3227 VT100.prototype.lf = function(count) {
3228   if (count == undefined) {
3229     count    = 1;
3230   } else {
3231     if (count > this.terminalHeight) {
3232       count  = this.terminalHeight;
3233     }
3234     if (count < 1) {
3235       count  = 1;
3236     }
3237   }
3238   while (count-- > 0) {
3239     if (this.cursorY == this.bottom - 1) {
3240       this.scrollRegion(0, this.top + 1,
3241                         this.terminalWidth, this.bottom - this.top - 1,
3242                         0, -1, this.color, this.style);
3243       offset = undefined;
3244     } else if (this.cursorY < this.terminalHeight - 1) {
3245       this.gotoXY(this.cursorX, this.cursorY + 1);
3246     }
3247   }
3248 };
3249
3250 VT100.prototype.ri = function(count) {
3251   if (count == undefined) {
3252     count   = 1;
3253   } else {
3254     if (count > this.terminalHeight) {
3255       count = this.terminalHeight;
3256     }
3257     if (count < 1) {
3258       count = 1;
3259     }
3260   }
3261   while (count-- > 0) {
3262     if (this.cursorY == this.top) {
3263       this.scrollRegion(0, this.top,
3264                         this.terminalWidth, this.bottom - this.top - 1,
3265                         0, 1, this.color, this.style);
3266     } else if (this.cursorY > 0) {
3267       this.gotoXY(this.cursorX, this.cursorY - 1);
3268     }
3269   }
3270   this.needWrap = false;
3271 };
3272
3273 VT100.prototype.respondID = function() {
3274   this.respondString += '\u001B[?6c';
3275 };
3276
3277 VT100.prototype.respondSecondaryDA = function() {
3278   this.respondString += '\u001B[>0;0;0c';
3279 };
3280
3281
3282 VT100.prototype.updateStyle = function() {
3283   this.style   = '';
3284   if (this.attr & ATTR_UNDERLINE) {
3285     this.style = 'text-decoration: underline;';
3286   }
3287   var bg       = (this.attr >> 4) & 0xF;
3288   var fg       =  this.attr       & 0xF;
3289   if (this.attr & ATTR_REVERSE) {
3290     var tmp    = bg;
3291     bg         = fg;
3292     fg         = tmp;
3293   }
3294   if ((this.attr & (ATTR_REVERSE | ATTR_DIM)) == ATTR_DIM) {
3295     fg         = 8; // Dark grey
3296   } else if (this.attr & ATTR_BRIGHT) {
3297     fg        |= 8;
3298     this.style = 'font-weight: bold;';
3299   }
3300   if (this.attr & ATTR_BLINK) {
3301     this.style = 'text-decoration: blink;';
3302   }
3303   this.color   = 'ansi' + fg + ' bgAnsi' + bg;
3304 };
3305
3306 VT100.prototype.setAttrColors = function(attr) {
3307   if (attr != this.attr) {
3308     this.attr = attr;
3309     this.updateStyle();
3310   }
3311 };
3312
3313 VT100.prototype.saveCursor = function() {
3314   this.savedX[this.currentScreen]     = this.cursorX;
3315   this.savedY[this.currentScreen]     = this.cursorY;
3316   this.savedAttr[this.currentScreen]  = this.attr;
3317   this.savedUseGMap                   = this.useGMap;
3318   for (var i = 0; i < 4; i++) {
3319     this.savedGMap[i]                 = this.GMap[i];
3320   }
3321   this.savedValid[this.currentScreen] = true;
3322 };
3323
3324 VT100.prototype.restoreCursor = function() {
3325   if (!this.savedValid[this.currentScreen]) {
3326     return;
3327   }
3328   this.attr      = this.savedAttr[this.currentScreen];
3329   this.updateStyle();
3330   this.useGMap   = this.savedUseGMap;
3331   for (var i = 0; i < 4; i++) {
3332     this.GMap[i] = this.savedGMap[i];
3333   }
3334   this.translate = this.GMap[this.useGMap];
3335   this.needWrap  = false;
3336   this.gotoXY(this.savedX[this.currentScreen],
3337               this.savedY[this.currentScreen]);
3338 };
3339
3340 VT100.prototype.getTransformName = function() {
3341   var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3342   for (var i = 0; i < styles.length; ++i) {
3343     if (typeof this.console[0].style[styles[i]] != 'undefined') {
3344       return styles[i];
3345     }
3346   }
3347   return undefined;
3348 };
3349
3350 VT100.prototype.getTransformStyle = function(transform, scale) {
3351   return scale && scale != 1.0
3352     ? transform == 'filter'
3353       ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3354                                  'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3355                                  "sizingMethod='auto expand')"
3356       : 'translateX(-50%) ' +
3357         'scaleX(' + (1.0/scale) + ') ' +
3358         'translateX(50%)'
3359     : '';
3360 };
3361
3362 VT100.prototype.set80_132Mode = function(state) {
3363   var transform                  = this.getTransformName();
3364   if (transform) {
3365     if ((this.console[this.currentScreen].style[transform] != '') == state) {
3366       return;
3367     }
3368     var style                    = state ?
3369                                    this.getTransformStyle(transform, 1.65):'';
3370     this.console[this.currentScreen].style[transform] = style;
3371     this.cursor.style[transform] = style;
3372     this.space.style[transform]  = style;
3373     this.scale                   = state ? 1.65 : 1.0;
3374     if (transform == 'filter') {
3375       this.console[this.currentScreen].style.width = state ? '165%' : '';
3376     }
3377     this.resizer();
3378   }
3379 };
3380
3381 VT100.prototype.setMode = function(state) {
3382   for (var i = 0; i <= this.npar; i++) {
3383     if (this.isQuestionMark) {
3384       switch (this.par[i]) {
3385       case  1: this.cursorKeyMode      = state;                      break;
3386       case  3: this.set80_132Mode(state);                            break;
3387       case  5: this.isInverted = state; this.refreshInvertedState(); break;
3388       case  6: this.offsetMode         = state;                      break;
3389       case  7: this.autoWrapMode       = state;                      break;
3390       case 1000:
3391       case  9: this.mouseReporting     = state;                      break;
3392       case 25: this.cursorNeedsShowing = state;
3393                if (state) { this.showCursor(); }
3394                else       { this.hideCursor(); }                     break;
3395       case 1047:
3396       case 1049:
3397       case 47: this.enableAlternateScreen(state);                    break;
3398       default:                                                       break;
3399       }
3400     } else {
3401       switch (this.par[i]) {
3402       case  3: this.dispCtrl           = state;                      break;
3403       case  4: this.insertMode         = state;                      break;
3404       case  20:this.crLfMode           = state;                      break;
3405       default:                                                       break;
3406       }
3407     }
3408   }
3409 };
3410
3411 VT100.prototype.statusReport = function() {
3412   // Ready and operational.
3413   this.respondString += '\u001B[0n';
3414 };
3415
3416 VT100.prototype.cursorReport = function() {
3417   this.respondString += '\u001B[' +
3418                         (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3419                         ';' +
3420                         (this.cursorX + 1) +
3421                         'R';
3422 };
3423
3424 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3425   // Changing of cursor color is not implemented.
3426 };
3427
3428 VT100.prototype.openPrinterWindow = function() {
3429   var rc            = true;
3430   try {
3431     if (!this.printWin || this.printWin.closed) {
3432       this.printWin = window.open('', 'print-output',
3433         'width=800,height=600,directories=no,location=no,menubar=yes,' +
3434         'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3435       this.printWin.document.body.innerHTML =
3436         '<link rel="stylesheet" href="' +
3437           document.location.protocol + '//' + document.location.host +
3438           document.location.pathname.replace(/[^/]*$/, '') +
3439           'print-styles.css" type="text/css">\n' +
3440         '<div id="options"><input id="autoprint" type="checkbox"' +
3441           (this.autoprint ? ' checked' : '') + '>' +
3442           'Automatically, print page(s) when job is ready' +
3443         '</input></div>\n' +
3444         '<div id="spacer"><input type="checkbox">&nbsp;</input></div>' +
3445         '<pre id="print"></pre>\n';
3446       var autoprint = this.printWin.document.getElementById('autoprint');
3447       this.addListener(autoprint, 'click',
3448                        (function(vt100, autoprint) {
3449                          return function() {
3450                            vt100.autoprint = autoprint.checked;
3451                            vt100.storeUserSettings();
3452                            return false;
3453                          };
3454                        })(this, autoprint));
3455       this.printWin.document.title = 'ShellInABox Printer Output';
3456     }
3457   } catch (e) {
3458     // Maybe, a popup blocker prevented us from working. Better catch the
3459     // exception, so that we won't break the entire terminal session. The
3460     // user probably needs to disable the blocker first before retrying the
3461     // operation.
3462     rc              = false;
3463   }
3464   rc               &= this.printWin && !this.printWin.closed &&
3465                       (this.printWin.innerWidth ||
3466                        this.printWin.document.documentElement.clientWidth ||
3467                        this.printWin.document.body.clientWidth) > 1;
3468
3469   if (!rc && this.printing == 100) {
3470     // Different popup blockers work differently. We try to detect a couple
3471     // of common methods. And then we retry again a brief amount later, as
3472     // false positives are otherwise possible. If we are sure that there is
3473     // a popup blocker in effect, we alert the user to it. This is helpful
3474     // as some popup blockers have minimal or no UI, and the user might not
3475     // notice that they are missing the popup. In any case, we only show at
3476     // most one message per print job.
3477     this.printing   = true;
3478     setTimeout((function(win) {
3479                   return function() {
3480                     if (!win || win.closed ||
3481                         (win.innerWidth ||
3482                          win.document.documentElement.clientWidth ||
3483                          win.document.body.clientWidth) <= 1) {
3484                       alert('Attempted to print, but a popup blocker ' +
3485                             'prevented the printer window from opening');
3486                     }
3487                   };
3488                 })(this.printWin), 2000);
3489   }
3490   return rc;
3491 };
3492
3493 VT100.prototype.sendToPrinter = function(s) {
3494   this.openPrinterWindow();
3495   try {
3496     var doc   = this.printWin.document;
3497     var print = doc.getElementById('print');
3498     if (print.lastChild && print.lastChild.nodeName == '#text') {
3499       print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3500     } else {
3501       print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3502     }
3503   } catch (e) {
3504     // There probably was a more aggressive popup blocker that prevented us
3505     // from accessing the printer windows.
3506   }
3507 };
3508
3509 VT100.prototype.sendControlToPrinter = function(ch) {
3510   // We get called whenever doControl() is active. But for the printer, we
3511   // only implement a basic line printer that doesn't understand most of
3512   // the escape sequences of the VT100 terminal. In fact, the only escape
3513   // sequence that we really need to recognize is '^[[5i' for turning the
3514   // printer off.
3515   try {
3516     switch (ch) {
3517     case  9:
3518       // HT
3519       this.openPrinterWindow();
3520       var doc                 = this.printWin.document;
3521       var print               = doc.getElementById('print');
3522       var chars               = print.lastChild &&
3523                                 print.lastChild.nodeName == '#text' ?
3524                                 print.lastChild.textContent.length : 0;
3525       this.sendToPrinter(this.spaces(8 - (chars % 8)));
3526       break;
3527     case 10:
3528       // CR
3529       break;
3530     case 12:
3531       // FF
3532       this.openPrinterWindow();
3533       var pageBreak           = this.printWin.document.createElement('div');
3534       pageBreak.className     = 'pagebreak';
3535       pageBreak.innerHTML     = '<hr />';
3536       this.printWin.document.getElementById('print').appendChild(pageBreak);
3537       break;
3538     case 13:
3539       // LF
3540       this.openPrinterWindow();
3541       var lineBreak           = this.printWin.document.createElement('br');
3542       this.printWin.document.getElementById('print').appendChild(lineBreak);
3543       break;
3544     case 27:
3545       // ESC
3546       this.isEsc              = ESesc;
3547       break;
3548     default:
3549       switch (this.isEsc) {
3550       case ESesc:
3551         this.isEsc            = ESnormal;
3552         switch (ch) {
3553         case 0x5B /*[*/:
3554           this.isEsc          = ESsquare;
3555           break;
3556         default:
3557           break;
3558         }
3559         break;
3560       case ESsquare:
3561         this.npar             = 0;
3562         this.par              = [ 0, 0, 0, 0, 0, 0, 0, 0,
3563                                   0, 0, 0, 0, 0, 0, 0, 0 ];
3564         this.isEsc            = ESgetpars;
3565         this.isQuestionMark   = ch == 0x3F /*?*/;
3566         if (this.isQuestionMark) {
3567           break;
3568         }
3569         // Fall through
3570       case ESgetpars: 
3571         if (ch == 0x3B /*;*/) {
3572           this.npar++;
3573           break;
3574         } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3575           var par             = this.par[this.npar];
3576           if (par == undefined) {
3577             par               = 0;
3578           }
3579           this.par[this.npar] = 10*par + (ch & 0xF);
3580           break;
3581         } else {
3582           this.isEsc          = ESgotpars;
3583         }
3584         // Fall through
3585       case ESgotpars:
3586         this.isEsc            = ESnormal;
3587         if (this.isQuestionMark) {
3588           break;
3589         }
3590         switch (ch) {
3591         case 0x69 /*i*/:
3592           this.csii(this.par[0]);
3593           break;
3594         default:
3595           break;
3596         }
3597         break;
3598       default:
3599         this.isEsc            = ESnormal;
3600         break;
3601       }
3602       break;
3603     }
3604   } catch (e) {
3605     // There probably was a more aggressive popup blocker that prevented us
3606     // from accessing the printer windows.
3607   }
3608 };
3609
3610 VT100.prototype.csiAt = function(number) {
3611   // Insert spaces
3612   if (number == 0) {
3613     number      = 1;
3614   }
3615   if (number > this.terminalWidth - this.cursorX) {
3616     number      = this.terminalWidth - this.cursorX;
3617   }
3618   this.scrollRegion(this.cursorX, this.cursorY,
3619                     this.terminalWidth - this.cursorX - number, 1,
3620                     number, 0, this.color, this.style);
3621   this.needWrap = false;
3622 };
3623
3624 VT100.prototype.csii = function(number) {
3625   // Printer control
3626   switch (number) {
3627   case 0: // Print Screen
3628     window.print();
3629     break;
3630   case 4: // Stop printing
3631     try {
3632       if (this.printing && this.printWin && !this.printWin.closed) {
3633         var print = this.printWin.document.getElementById('print');
3634         while (print.lastChild &&
3635                print.lastChild.tagName == 'DIV' &&
3636                print.lastChild.className == 'pagebreak') {
3637           // Remove trailing blank pages
3638           print.removeChild(print.lastChild);
3639         }
3640         if (this.autoprint) {
3641           this.printWin.print();
3642         }
3643       }
3644     } catch (e) {
3645     }
3646     this.printing = false;
3647     break;
3648   case 5: // Start printing
3649     if (!this.printing && this.printWin && !this.printWin.closed) {
3650       this.printWin.document.getElementById('print').innerHTML = '';
3651     }
3652     this.printing = 100;
3653     break;
3654   default:
3655     break;
3656   }
3657 };
3658
3659 VT100.prototype.csiJ = function(number) {
3660   switch (number) {
3661   case 0: // Erase from cursor to end of display
3662     this.clearRegion(this.cursorX, this.cursorY,
3663                      this.terminalWidth - this.cursorX, 1,
3664                      this.color, this.style);
3665     if (this.cursorY < this.terminalHeight-2) {
3666       this.clearRegion(0, this.cursorY+1,
3667                        this.terminalWidth, this.terminalHeight-this.cursorY-1,
3668                        this.color, this.style);
3669     }
3670     break;
3671   case 1: // Erase from start to cursor
3672     if (this.cursorY > 0) {
3673       this.clearRegion(0, 0,
3674                        this.terminalWidth, this.cursorY,
3675                        this.color, this.style);
3676     }
3677     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3678                      this.color, this.style);
3679     break;
3680   case 2: // Erase whole display
3681     this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3682                      this.color, this.style);
3683     break;
3684   default:
3685     return;
3686   }
3687   needWrap = false;
3688 };
3689
3690 VT100.prototype.csiK = function(number) {
3691   switch (number) {
3692   case 0: // Erase from cursor to end of line
3693     this.clearRegion(this.cursorX, this.cursorY,
3694                      this.terminalWidth - this.cursorX, 1,
3695                      this.color, this.style);
3696     break;
3697   case 1: // Erase from start of line to cursor
3698     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3699                      this.color, this.style);
3700     break;
3701   case 2: // Erase whole line
3702     this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3703                      this.color, this.style);
3704     break;
3705   default:
3706     return;
3707   }
3708   needWrap = false;
3709 };
3710
3711 VT100.prototype.csiL = function(number) {
3712   // Open line by inserting blank line(s)
3713   if (this.cursorY >= this.bottom) {
3714     return;
3715   }
3716   if (number == 0) {
3717     number = 1;
3718   }
3719   if (number > this.bottom - this.cursorY) {
3720     number = this.bottom - this.cursorY;
3721   }
3722   this.scrollRegion(0, this.cursorY,
3723                     this.terminalWidth, this.bottom - this.cursorY - number,
3724                     0, number, this.color, this.style);
3725   needWrap = false;
3726 };
3727
3728 VT100.prototype.csiM = function(number) {
3729   // Delete line(s), scrolling up the bottom of the screen.
3730   if (this.cursorY >= this.bottom) {
3731     return;
3732   }
3733   if (number == 0) {
3734     number = 1;
3735   }
3736   if (number > this.bottom - this.cursorY) {
3737     number = bottom - cursorY;
3738   }
3739   this.scrollRegion(0, this.cursorY + number,
3740                     this.terminalWidth, this.bottom - this.cursorY - number,
3741                     0, -number, this.color, this.style);
3742   needWrap = false;
3743 };
3744
3745 VT100.prototype.csim = function() {
3746   for (var i = 0; i <= this.npar; i++) {
3747     switch (this.par[i]) {
3748     case 0:  this.attr  = ATTR_DEFAULT;                                break;
3749     case 1:  this.attr  = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT;         break;
3750     case 2:  this.attr  = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM;         break;
3751     case 4:  this.attr |= ATTR_UNDERLINE;                              break;
3752     case 5:  this.attr |= ATTR_BLINK;                                  break;
3753     case 7:  this.attr |= ATTR_REVERSE;                                break;
3754     case 10:
3755       this.translate    = this.GMap[this.useGMap];
3756       this.dispCtrl     = false;
3757       this.toggleMeta   = false;
3758       break;
3759     case 11:
3760       this.translate    = this.CodePage437Map;
3761       this.dispCtrl     = true;
3762       this.toggleMeta   = false;
3763       break;
3764     case 12:
3765       this.translate    = this.CodePage437Map;
3766       this.dispCtrl     = true;
3767       this.toggleMeta   = true;
3768       break;
3769     case 21:
3770     case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM);                     break;
3771     case 24: this.attr &= ~ ATTR_UNDERLINE;                            break;
3772     case 25: this.attr &= ~ ATTR_BLINK;                                break;
3773     case 27: this.attr &= ~ ATTR_REVERSE;                              break;
3774     case 38: this.attr  = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|
3775                           ATTR_UNDERLINE;                              break;
3776     case 39: this.attr &= ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F); break;
3777     case 49: this.attr |= 0xF0;                                        break;
3778     default:
3779       if (this.par[i] >= 30 && this.par[i] <= 37) {
3780           var fg        = this.par[i] - 30;
3781           this.attr     = (this.attr & ~0x0F) | fg;
3782       } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3783           var bg        = this.par[i] - 40;
3784           this.attr     = (this.attr & ~0xF0) | (bg << 4);
3785       }
3786       break;
3787     }
3788   }
3789   this.updateStyle();
3790 };
3791
3792 VT100.prototype.csiP = function(number) {
3793   // Delete character(s) following cursor
3794   if (number == 0) {
3795     number = 1;
3796   }
3797   if (number > this.terminalWidth - this.cursorX) {
3798     number = this.terminalWidth - this.cursorX;
3799   }
3800   this.scrollRegion(this.cursorX + number, this.cursorY,
3801                     this.terminalWidth - this.cursorX - number, 1,
3802                     -number, 0, this.color, this.style);
3803   needWrap = false;
3804 };
3805
3806 VT100.prototype.csiX = function(number) {
3807   // Clear characters following cursor
3808   if (number == 0) {
3809     number++;
3810   }
3811   if (number > this.terminalWidth - this.cursorX) {
3812     number = this.terminalWidth - this.cursorX;
3813   }
3814   this.clearRegion(this.cursorX, this.cursorY, number, 1,
3815                    this.color, this.style);
3816   needWrap = false;
3817 };
3818
3819 VT100.prototype.settermCommand = function() {
3820   // Setterm commands are not implemented
3821 };
3822
3823 VT100.prototype.doControl = function(ch) {
3824   if (this.printing) {
3825     this.sendControlToPrinter(ch);
3826     return '';
3827   }
3828   var lineBuf                = '';
3829   switch (ch) {
3830   case 0x00: /* ignored */                                              break;
3831   case 0x08: this.bs();                                                 break;
3832   case 0x09: this.ht();                                                 break;
3833   case 0x0A:
3834   case 0x0B:
3835   case 0x0C:
3836   case 0x84: this.lf(); if (!this.crLfMode)                             break;
3837   case 0x0D: this.cr();                                                 break;
3838   case 0x85: this.cr(); this.lf();                                      break;
3839   case 0x0E: this.useGMap     = 1;
3840              this.translate   = this.GMap[1];
3841              this.dispCtrl    = true;                                   break;
3842   case 0x0F: this.useGMap     = 0;
3843              this.translate   = this.GMap[0];
3844              this.dispCtrl    = false;                                  break;
3845   case 0x18:
3846   case 0x1A: this.isEsc       = ESnormal;                               break;
3847   case 0x1B: this.isEsc       = ESesc;                                  break;
3848   case 0x7F: /* ignored */                                              break;
3849   case 0x88: this.userTabStop[this.cursorX] = true;                     break;
3850   case 0x8D: this.ri();                                                 break;
3851   case 0x8E: this.isEsc       = ESss2;                                  break;
3852   case 0x8F: this.isEsc       = ESss3;                                  break;
3853   case 0x9A: this.respondID();                                          break;
3854   case 0x9B: this.isEsc       = ESsquare;                               break;
3855   case 0x07: if (this.isEsc != EStitle) {
3856                this.beep();                                             break;
3857              }
3858              /* fall thru */
3859   default:   switch (this.isEsc) {
3860     case ESesc:
3861       this.isEsc              = ESnormal;
3862       switch (ch) {
3863 /*%*/ case 0x25: this.isEsc   = ESpercent;                              break;
3864 /*(*/ case 0x28: this.isEsc   = ESsetG0;                                break;
3865 /*-*/ case 0x2D:
3866 /*)*/ case 0x29: this.isEsc   = ESsetG1;                                break;
3867 /*.*/ case 0x2E:
3868 /***/ case 0x2A: this.isEsc   = ESsetG2;                                break;
3869 /*/*/ case 0x2F:
3870 /*+*/ case 0x2B: this.isEsc   = ESsetG3;                                break;
3871 /*#*/ case 0x23: this.isEsc   = EShash;                                 break;
3872 /*7*/ case 0x37: this.saveCursor();                                     break;
3873 /*8*/ case 0x38: this.restoreCursor();                                  break;
3874 /*>*/ case 0x3E: this.applKeyMode = false;                              break;
3875 /*=*/ case 0x3D: this.applKeyMode = true;                               break;
3876 /*D*/ case 0x44: this.lf();                                             break;
3877 /*E*/ case 0x45: this.cr(); this.lf();                                  break;
3878 /*M*/ case 0x4D: this.ri();                                             break;
3879 /*N*/ case 0x4E: this.isEsc   = ESss2;                                  break;
3880 /*O*/ case 0x4F: this.isEsc   = ESss3;                                  break;
3881 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true;                 break;
3882 /*Z*/ case 0x5A: this.respondID();                                      break;
3883 /*[*/ case 0x5B: this.isEsc   = ESsquare;                               break;
3884 /*]*/ case 0x5D: this.isEsc   = ESnonstd;                               break;
3885 /*c*/ case 0x63: this.reset();                                          break;
3886 /*g*/ case 0x67: this.flashScreen();                                    break;
3887       default:                                                          break;
3888       }
3889       break;
3890     case ESnonstd:
3891       switch (ch) {
3892 /*0*/ case 0x30:
3893 /*1*/ case 0x31:
3894 /*2*/ case 0x32: this.isEsc   = EStitle; this.titleString = '';         break;
3895 /*P*/ case 0x50: this.npar    = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3896                  this.isEsc   = ESpalette;                              break;
3897 /*R*/ case 0x52: // Palette support is not implemented
3898                  this.isEsc   = ESnormal;                               break;
3899       default:   this.isEsc   = ESnormal;                               break;
3900       }
3901       break;
3902     case ESpalette:
3903       if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3904           (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3905           (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3906         this.par[this.npar++] = ch > 0x39  /*9*/ ? (ch & 0xDF) - 55
3907                                                 : (ch & 0xF);
3908         if (this.npar == 7) {
3909           // Palette support is not implemented
3910           this.isEsc          = ESnormal;
3911         }
3912       } else {
3913         this.isEsc            = ESnormal;
3914       }
3915       break;
3916     case ESsquare:
3917       this.npar               = 0;
3918       this.par                = [ 0, 0, 0, 0, 0, 0, 0, 0,
3919                                   0, 0, 0, 0, 0, 0, 0, 0 ];
3920       this.isEsc              = ESgetpars;
3921 /*[*/ if (ch == 0x5B) { // Function key
3922         this.isEsc            = ESfunckey;
3923         break;
3924       } else {
3925 /*?*/   this.isQuestionMark   = ch == 0x3F;
3926         if (this.isQuestionMark) {
3927           break;
3928         }
3929       }
3930       // Fall through
3931     case ESdeviceattr:
3932     case ESgetpars: 
3933 /*;*/ if (ch == 0x3B) {
3934         this.npar++;
3935         break;
3936       } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3937         var par               = this.par[this.npar];
3938         if (par == undefined) {
3939           par                 = 0;
3940         }
3941         this.par[this.npar]   = 10*par + (ch & 0xF);
3942         break;
3943       } else if (this.isEsc == ESdeviceattr) {
3944         switch (ch) {
3945 /*c*/   case 0x63: if (this.par[0] == 0) this.respondSecondaryDA();     break;
3946 /*m*/   case 0x6D: /* (re)set key modifier resource values */           break;
3947 /*n*/   case 0x6E: /* disable key modifier resource values */           break;
3948 /*p*/   case 0x70: /* set pointer mode resource value */                break;
3949         default:                                                        break;
3950         }
3951         this.isEsc            = ESnormal;
3952         break;
3953       } else {
3954         this.isEsc            = ESgotpars;
3955       }
3956       // Fall through
3957     case ESgotpars:
3958       this.isEsc              = ESnormal;
3959       if (this.isQuestionMark) {
3960         switch (ch) {
3961 /*h*/   case 0x68: this.setMode(true);                                  break;
3962 /*l*/   case 0x6C: this.setMode(false);                                 break;
3963 /*c*/   case 0x63: this.setCursorAttr(this.par[2], this.par[1]);        break;
3964         default:                                                        break;
3965         }
3966         this.isQuestionMark   = false;
3967         break;
3968       }
3969       switch (ch) {
3970 /*!*/ case 0x21: this.isEsc   = ESbang;                                 break;
3971 /*>*/ case 0x3E: if (!this.npar) this.isEsc  = ESdeviceattr;            break;
3972 /*G*/ case 0x47:
3973 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY);            break;
3974 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3975                              this.cursorY - (this.par[0] ? this.par[0] : 1));
3976                                                                         break;
3977 /*B*/ case 0x42:
3978 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3979                              this.cursorY + (this.par[0] ? this.par[0] : 1));
3980                                                                         break;
3981 /*C*/ case 0x43:
3982 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3983                              this.cursorY);                             break;
3984 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3985                              this.cursorY);                             break;
3986 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3987                                                                         break;
3988 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3989                                                                         break;
3990 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1);           break;
3991 /*H*/ case 0x48:
3992 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1);        break;
3993 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1);                break;
3994 /*@*/ case 0x40: this.csiAt(this.par[0]);                               break;
3995 /*i*/ case 0x69: this.csii(this.par[0]);                                break;
3996 /*J*/ case 0x4A: this.csiJ(this.par[0]);                                break;
3997 /*K*/ case 0x4B: this.csiK(this.par[0]);                                break;
3998 /*L*/ case 0x4C: this.csiL(this.par[0]);                                break;
3999 /*M*/ case 0x4D: this.csiM(this.par[0]);                                break;
4000 /*m*/ case 0x6D: this.csim();                                           break;
4001 /*P*/ case 0x50: this.csiP(this.par[0]);                                break;
4002 /*X*/ case 0x58: this.csiX(this.par[0]);                                break;
4003 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1);                break;
4004 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1);                break;
4005 /*c*/ case 0x63: if (!this.par[0]) this.respondID();                    break;
4006 /*g*/ case 0x67: if (this.par[0] == 0) {
4007                    this.userTabStop[this.cursorX] = false;
4008                  } else if (this.par[0] == 2 || this.par[0] == 3) {
4009                    this.userTabStop               = [ ];
4010                    for (var i = 0; i < this.terminalWidth; i++) {
4011                      this.userTabStop[i]          = false;
4012                    }
4013                  }
4014                  break;
4015 /*h*/ case 0x68: this.setMode(true);                                    break;
4016 /*l*/ case 0x6C: this.setMode(false);                                   break;
4017 /*n*/ case 0x6E: switch (this.par[0]) {
4018                  case 5: this.statusReport();                           break;
4019                  case 6: this.cursorReport();                           break;
4020                  default:                                               break;
4021                  }
4022                  break;
4023 /*q*/ case 0x71: // LED control not implemented
4024                                                                         break;
4025 /*r*/ case 0x72: var t        = this.par[0] ? this.par[0] : 1;
4026                  var b        = this.par[1] ? this.par[1]
4027                                             : this.terminalHeight;
4028                  if (t < b && b <= this.terminalHeight) {
4029                    this.top   = t - 1;
4030                    this.bottom= b;
4031                    this.gotoXaY(0, 0);
4032                  }
4033                  break;
4034 /*b*/ case 0x62: var c        = this.par[0] ? this.par[0] : 1;
4035                  if (c > this.terminalWidth * this.terminalHeight) {
4036                    c          = this.terminalWidth * this.terminalHeight;
4037                  }
4038                  while (c-- > 0) {
4039                    lineBuf   += this.lastCharacter;
4040                  }
4041                  break;
4042 /*s*/ case 0x73: this.saveCursor();                                     break;
4043 /*u*/ case 0x75: this.restoreCursor();                                  break;
4044 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1);                break;
4045 /*]*/ case 0x5D: this.settermCommand();                                 break;
4046       default:                                                          break;
4047       }
4048       break;
4049     case ESbang:
4050       if (ch == 'p') {
4051         this.reset();
4052       }
4053       this.isEsc              = ESnormal;
4054       break;
4055     case ESpercent:
4056       this.isEsc              = ESnormal;
4057       switch (ch) {
4058 /*@*/ case 0x40: this.utfEnabled = false;                               break;
4059 /*G*/ case 0x47:
4060 /*8*/ case 0x38: this.utfEnabled = true;                                break;
4061       default:                                                          break;
4062       }
4063       break;
4064     case ESfunckey:
4065       this.isEsc              = ESnormal;                               break;
4066     case EShash:
4067       this.isEsc              = ESnormal;
4068 /*8*/ if (ch == 0x38) {
4069         // Screen alignment test not implemented
4070       }
4071       break;
4072     case ESsetG0:
4073     case ESsetG1:
4074     case ESsetG2:
4075     case ESsetG3:
4076       var g                   = this.isEsc - ESsetG0;
4077       this.isEsc              = ESnormal;
4078       switch (ch) {
4079 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap;                  break;
4080 /*A*/ case 0x42:
4081 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map;                         break;
4082 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map;                    break;
4083 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap;                   break;
4084       default:                                                          break;
4085       }
4086       if (this.useGMap == g) {
4087         this.translate        = this.GMap[g];
4088       }
4089       break;
4090     case EStitle:
4091       if (ch == 0x07) {
4092         if (this.titleString && this.titleString.charAt(0) == ';') {
4093           this.titleString    = this.titleString.substr(1);
4094           if (this.titleString != '') {
4095             this.titleString += ' - ';
4096           }
4097           this.titleString += 'Shell In A Box'
4098         }
4099         try {
4100           window.document.title = this.titleString;
4101         } catch (e) {
4102         }
4103         this.isEsc            = ESnormal;
4104       } else {
4105         this.titleString     += String.fromCharCode(ch);
4106       }
4107       break;
4108     case ESss2:
4109     case ESss3:
4110       if (ch < 256) {
4111           ch                  = this.GMap[this.isEsc - ESss2 + 2]
4112                                          [this.toggleMeta ? (ch | 0x80) : ch];
4113         if ((ch & 0xFF00) == 0xF000) {
4114           ch                  = ch & 0xFF;
4115         } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4116           this.isEsc         = ESnormal;                                break;
4117         }
4118       }
4119       this.lastCharacter      = String.fromCharCode(ch);
4120       lineBuf                += this.lastCharacter;
4121       this.isEsc              = ESnormal;                               break;
4122     default:
4123       this.isEsc              = ESnormal;                               break;
4124     }
4125     break;
4126   }
4127   return lineBuf;
4128 };
4129
4130 VT100.prototype.renderString = function(s, showCursor) {
4131   if (this.printing) {
4132     this.sendToPrinter(s);
4133     if (showCursor) {
4134       this.showCursor();
4135     }
4136     return;
4137   }
4138
4139   // We try to minimize the number of DOM operations by coalescing individual
4140   // characters into strings. This is a significant performance improvement.
4141   var incX = s.length;
4142   if (incX > this.terminalWidth - this.cursorX) {
4143     incX   = this.terminalWidth - this.cursorX;
4144     if (incX <= 0) {
4145       return;
4146     }
4147     s      = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4148   }
4149   if (showCursor) {
4150     // Minimize the number of calls to putString(), by avoiding a direct
4151     // call to this.showCursor()
4152     this.cursor.style.visibility = '';
4153   }
4154   this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4155 };
4156
4157 VT100.prototype.vt100 = function(s) {
4158   this.cursorNeedsShowing = this.hideCursor();
4159   this.respondString      = '';
4160   var lineBuf             = '';
4161   for (var i = 0; i < s.length; i++) {
4162     var ch = s.charCodeAt(i);
4163     if (this.utfEnabled) {
4164       // Decode UTF8 encoded character
4165       if (ch > 0x7F) {
4166         if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4167           this.utfChar    = (this.utfChar << 6) | (ch & 0x3F);
4168           if (--this.utfCount <= 0) {
4169             if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4170               ch = 0xFFFD;
4171             } else {
4172               ch          = this.utfChar;
4173             }
4174           } else {
4175             continue;
4176           }
4177         } else {
4178           if ((ch & 0xE0) == 0xC0) {
4179             this.utfCount = 1;
4180             this.utfChar  = ch & 0x1F;
4181           } else if ((ch & 0xF0) == 0xE0) {
4182             this.utfCount = 2;
4183             this.utfChar  = ch & 0x0F;
4184           } else if ((ch & 0xF8) == 0xF0) {
4185             this.utfCount = 3;
4186             this.utfChar  = ch & 0x07;
4187           } else if ((ch & 0xFC) == 0xF8) {
4188             this.utfCount = 4;
4189             this.utfChar  = ch & 0x03;
4190           } else if ((ch & 0xFE) == 0xFC) {
4191             this.utfCount = 5;
4192             this.utfChar  = ch & 0x01;
4193           } else {
4194             this.utfCount = 0;
4195           }
4196           continue;
4197         }
4198       } else {
4199         this.utfCount     = 0;
4200       }
4201     }
4202     var isNormalCharacter =
4203       (ch >= 32 && ch <= 127 || ch >= 160 ||
4204        this.utfEnabled && ch >= 128 ||
4205        !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4206       (ch != 0x7F || this.dispCtrl);
4207     
4208     if (isNormalCharacter && this.isEsc == ESnormal) {
4209       if (ch < 256) {
4210         ch                = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4211       }
4212       if ((ch & 0xFF00) == 0xF000) {
4213         ch                = ch & 0xFF;
4214       } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4215         continue;
4216       }
4217       if (!this.printing) {
4218         if (this.needWrap || this.insertMode) {
4219           if (lineBuf) {
4220             this.renderString(lineBuf);
4221             lineBuf       = '';
4222           }
4223         }
4224         if (this.needWrap) {
4225           this.cr(); this.lf();
4226         }
4227         if (this.insertMode) {
4228           this.scrollRegion(this.cursorX, this.cursorY,
4229                             this.terminalWidth - this.cursorX - 1, 1,
4230                             1, 0, this.color, this.style);
4231         }
4232       }
4233       this.lastCharacter  = String.fromCharCode(ch);
4234       lineBuf            += this.lastCharacter;
4235       if (!this.printing &&
4236           this.cursorX + lineBuf.length >= this.terminalWidth) {
4237         this.needWrap     = this.autoWrapMode;
4238       }
4239     } else {
4240       if (lineBuf) {
4241         this.renderString(lineBuf);
4242         lineBuf           = '';
4243       }
4244       var expand          = this.doControl(ch);
4245       if (expand.length) {
4246         var r             = this.respondString;
4247         this.respondString= r + this.vt100(expand);
4248       }
4249     }
4250   }
4251   if (lineBuf) {
4252     this.renderString(lineBuf, this.cursorNeedsShowing);
4253   } else if (this.cursorNeedsShowing) {
4254     this.showCursor();
4255   }
4256   return this.respondString;
4257 };
4258
4259 VT100.prototype.Latin1Map = [
4260 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4261 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4262 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4263 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4264 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4265 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4266 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4267 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4268 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4269 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4270 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4271 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4272 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4273 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4274 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4275 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4276 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4277 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4278 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4279 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4280 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4281 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4282 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4283 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4284 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4285 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4286 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4287 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4288 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4289 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4290 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4291 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4292 ];
4293
4294 VT100.prototype.VT100GraphicsMap = [
4295 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4296 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4297 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4298 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4299 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4300 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4301 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4302 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4303 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4304 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4305 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4306 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4307 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4308 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4309 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4310 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4311 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4312 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4313 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4314 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4315 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4316 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4317 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4318 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4319 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4320 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4321 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4322 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4323 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4324 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4325 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4326 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4327 ];
4328
4329 VT100.prototype.CodePage437Map = [
4330 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4331 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4332 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4333 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4334 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4335 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4336 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4337 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4338 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4339 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4340 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4341 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4342 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4343 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4344 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4345 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4346 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4347 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4348 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4349 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4350 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4351 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4352 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4353 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4354 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4355 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4356 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4357 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4358 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4359 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4360 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4361 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4362 ];
4363
4364 VT100.prototype.DirectToFontMap = [
4365 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4366 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4367 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4368 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4369 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4370 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4371 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4372 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4373 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4374 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4375 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4376 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4377 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4378 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4379 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4380 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4381 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4382 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4383 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4384 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4385 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4386 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4387 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4388 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4389 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4390 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4391 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4392 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4393 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4394 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4395 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4396 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4397 ];
4398
4399 VT100.prototype.ctrlAction = [
4400   true,  false, false, false, false, false, false, true,
4401   true,  true,  true,  true,  true,  true,  true,  true,
4402   false, false, false, false, false, false, false, false,
4403   true,  false, true,  true,  false, false, false, false
4404 ];
4405
4406 VT100.prototype.ctrlAlways = [
4407   true,  false, false, false, false, false, false, false,
4408   true,  false, true,  false, true,  true,  true,  true,
4409   false, false, false, false, false, false, false, false,
4410   false, false, false, true,  false, false, false, false
4411 ];
4412
This page took 0.380176 seconds and 5 git commands to generate.