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