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