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