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