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