]> andersk Git - test.git/blob - shellinabox/vt100.jspp
Fix handling of control and capslock keys.
[test.git] / shellinabox / vt100.jspp
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                            = 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                             = 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, MOUSE_DOWN));
651   this.addListener(this.scrollable,'mouseup',  mouseEvent(this, MOUSE_UP));
652   this.addListener(this.scrollable,'click',    mouseEvent(this, 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 == MOUSE_UP || type == 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 != 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 != MOUSE_DOWN || !event.shiftKey)) {
962     if (inside || type != 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 != 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 == 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 " + VERSION +
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.applyModifiers = function(ch, event) {
2059   if (ch) {
2060     if (event.ctrlKey) {
2061       if (ch >= 32 && ch <= 127) {
2062         // For historic reasons, some control characters are treated specially
2063         switch (ch) {
2064         case /* 3 */ 51: ch  =  27; break;
2065         case /* 4 */ 52: ch  =  28; break;
2066         case /* 5 */ 53: ch  =  29; break;
2067         case /* 6 */ 54: ch  =  30; break;
2068         case /* 7 */ 55: ch  =  31; break;
2069         case /* 8 */ 56: ch  = 127; break;
2070         case /* ? */ 63: ch  = 127; break;
2071         default:         ch &=  31; break;
2072         }
2073       }
2074     }
2075     return String.fromCharCode(ch);
2076   } else {
2077     return undefined;
2078   }
2079 };
2080
2081 VT100.prototype.handleKey = function(event) {
2082   // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2083   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2084   //             event.metaKey ? ', ' +
2085   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2086   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2087   //            '\r\n');
2088   var ch, key;
2089   if (typeof event.charCode != 'undefined') {
2090     // non-IE keypress events have a translated charCode value. Also, our
2091     // fake events generated when receiving keydown events include this data
2092     // on all browsers.
2093     ch                                = event.charCode;
2094     key                               = event.keyCode;
2095   } else {
2096     // When sending a keypress event, IE includes the translated character
2097     // code in the keyCode field.
2098     ch                                = event.keyCode;
2099     key                               = undefined;
2100   }
2101
2102   // Apply modifier keys (ctrl and shift)
2103   if (ch) {
2104     key                               = undefined;
2105   }
2106   ch                                  = this.applyModifiers(ch, event);
2107
2108   // By this point, "ch" is either defined and contains the character code, or
2109   // it is undefined and "key" defines the code of a function key 
2110   if (ch != undefined) {
2111     this.scrollable.scrollTop         = this.numScrollbackLines *
2112                                         this.cursorHeight + 1;
2113   } else {
2114     if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2115       // Many programs have difficulties dealing with parametrized escape
2116       // sequences for function keys. Thus, if ALT is the only modifier
2117       // key, return Emacs-style keycodes for commonly used keys.
2118       switch (key) {
2119       case  33: /* Page Up      */ ch = '\u001B<';                      break;
2120       case  34: /* Page Down    */ ch = '\u001B>';                      break;
2121       case  37: /* Left         */ ch = '\u001Bb';                      break;
2122       case  38: /* Up           */ ch = '\u001Bp';                      break;
2123       case  39: /* Right        */ ch = '\u001Bf';                      break;
2124       case  40: /* Down         */ ch = '\u001Bn';                      break;
2125       case  46: /* Delete       */ ch = '\u001Bd';                      break;
2126       default:                                                          break;
2127       }
2128     } else if (event.shiftKey && !event.ctrlKey &&
2129                !event.altKey && !event.metaKey) {
2130       switch (key) {
2131       case  33: /* Page Up      */ this.scrollBack();                   return;
2132       case  34: /* Page Down    */ this.scrollFore();                   return;
2133       default:                                                          break;
2134       }
2135     }
2136     if (ch == undefined) {
2137       switch (key) {
2138       case   8: /* Backspace    */ ch = '\u007f';                       break;
2139       case   9: /* Tab          */ ch = '\u0009';                       break;
2140       case  10: /* Return       */ ch = '\u000A';                       break;
2141       case  13: /* Enter        */ ch = this.crLfMode ?
2142                                         '\r\n' : '\r';                  break;
2143       case  16: /* Shift        */                                      return;
2144       case  17: /* Ctrl         */                                      return;
2145       case  18: /* Alt          */                                      return;
2146       case  19: /* Break        */                                      return;
2147       case  20: /* Caps Lock    */                                      return;
2148       case  27: /* Escape       */ ch = '\u001B';                       break;
2149       case  33: /* Page Up      */ ch = '\u001B[5~';                    break;
2150       case  34: /* Page Down    */ ch = '\u001B[6~';                    break;
2151       case  35: /* End          */ ch = '\u001BOF';                     break;
2152       case  36: /* Home         */ ch = '\u001BOH';                     break;
2153       case  37: /* Left         */ ch = this.cursorKeyMode ?
2154                              '\u001BOD' : '\u001B[D';                   break;
2155       case  38: /* Up           */ ch = this.cursorKeyMode ?
2156                              '\u001BOA' : '\u001B[A';                   break;
2157       case  39: /* Right        */ ch = this.cursorKeyMode ?
2158                              '\u001BOC' : '\u001B[C';                   break;
2159       case  40: /* Down         */ ch = this.cursorKeyMode ?
2160                              '\u001BOB' : '\u001B[B';                   break;
2161       case  45: /* Insert       */ ch = '\u001B[2~';                    break;
2162       case  46: /* Delete       */ ch = '\u001B[3~';                    break;
2163       case  91: /* Left Window  */                                      return;
2164       case  92: /* Right Window */                                      return;
2165       case  93: /* Select       */                                      return;
2166       case  96: /* 0            */ ch = this.applyModifiers(48, event); break;
2167       case  97: /* 1            */ ch = this.applyModifiers(49, event); break;
2168       case  98: /* 2            */ ch = this.applyModifiers(50, event); break;
2169       case  99: /* 3            */ ch = this.applyModifiers(51, event); break;
2170       case 100: /* 4            */ ch = this.applyModifiers(52, event); break;
2171       case 101: /* 5            */ ch = this.applyModifiers(53, event); break;
2172       case 102: /* 6            */ ch = this.applyModifiers(54, event); break;
2173       case 103: /* 7            */ ch = this.applyModifiers(55, event); break;
2174       case 104: /* 8            */ ch = this.applyModifiers(56, event); break;
2175       case 105: /* 9            */ ch = this.applyModifiers(58, event); break;
2176       case 106: /* *            */ ch = this.applyModifiers(42, event); break;
2177       case 107: /* +            */ ch = this.applyModifiers(43, event); break;
2178       case 109: /* -            */ ch = this.applyModifiers(45, event); break;
2179       case 110: /* .            */ ch = this.applyModifiers(46, event); break;
2180       case 111: /* /            */ ch = this.applyModifiers(47, event); break;
2181       case 112: /* F1           */ ch = '\u001BOP';                     break;
2182       case 113: /* F2           */ ch = '\u001BOQ';                     break;
2183       case 114: /* F3           */ ch = '\u001BOR';                     break;
2184       case 115: /* F4           */ ch = '\u001BOS';                     break;
2185       case 116: /* F5           */ ch = '\u001B[15~';                   break;
2186       case 117: /* F6           */ ch = '\u001B[17~';                   break;
2187       case 118: /* F7           */ ch = '\u001B[18~';                   break;
2188       case 119: /* F8           */ ch = '\u001B[19~';                   break;
2189       case 120: /* F9           */ ch = '\u001B[20~';                   break;
2190       case 121: /* F10          */ ch = '\u001B[21~';                   break;
2191       case 122: /* F11          */ ch = '\u001B[23~';                   break;
2192       case 123: /* F12          */ ch = '\u001B[24~';                   break;
2193       case 144: /* Num Lock     */                                      return;
2194       case 145: /* Scroll Lock  */                                      return;
2195       case 186: /* ;            */ ch = this.applyModifiers(59, event); break;
2196       case 187: /* =            */ ch = this.applyModifiers(61, event); break;
2197       case 188: /* ,            */ ch = this.applyModifiers(44, event); break;
2198       case 189: /* -            */ ch = this.applyModifiers(45, event); break;
2199       case 190: /* .            */ ch = this.applyModifiers(46, event); break;
2200       case 191: /* /            */ ch = this.applyModifiers(47, event); break;
2201       case 192: /* `            */ ch = this.applyModifiers(96, event); break;
2202       case 219: /* [            */ ch = this.applyModifiers(91, event); break;
2203       case 220: /* \            */ ch = this.applyModifiers(92, event); break;
2204       case 221: /* ]            */ ch = this.applyModifiers(93, event); break;
2205       case 222: /* '            */ ch = this.applyModifiers(39, event); break;
2206       default:                                                          return;
2207       }
2208       this.scrollable.scrollTop       = this.numScrollbackLines *
2209                                         this.cursorHeight + 1;
2210     }
2211   }
2212
2213   // "ch" now contains the sequence of keycodes to send. But we might still
2214   // have to apply the effects of modifier keys.
2215   if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2216     var start, digit, part1, part2;
2217     if ((start = ch.substr(0, 2)) == '\u001B[') {
2218       for (part1 = start;
2219            part1.length < ch.length &&
2220              (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2221         part1                         = ch.substr(0, part1.length + 1);
2222       }
2223       part2                           = ch.substr(part1.length);
2224       if (part1.length > 2) {
2225         part1                        += ';';
2226       }
2227     } else if (start == '\u001BO') {
2228       part1                           = start;
2229       part2                           = ch.substr(2);
2230     }
2231     if (part1 != undefined) {
2232       ch                              = part1                                 +
2233                                        ((event.shiftKey             ? 1 : 0)  +
2234                                         (event.altKey|event.metaKey ? 2 : 0)  +
2235                                         (event.ctrlKey              ? 4 : 0)) +
2236                                         part2;
2237     } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2238       ch                              = '\u001B' + ch;
2239     }
2240   }
2241
2242   if (this.menu.style.visibility == 'hidden') {
2243     // this.vt100('R: c=');
2244     // for (var i = 0; i < ch.length; i++)
2245     //   this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2246     // this.vt100('\r\n');
2247     this.keysPressed(ch);
2248   }
2249 };
2250
2251 VT100.prototype.inspect = function(o, d) {
2252   if (d == undefined) {
2253     d       = 0;
2254   }
2255   var rc    = '';
2256   if (typeof o == 'object' && ++d < 2) {
2257     rc      = '[\r\n';
2258     for (i in o) {
2259       rc   += this.spaces(d * 2) + i + ' -> ';
2260       try {
2261         rc += this.inspect(o[i], d);
2262       } catch (e) {
2263         rc += '?' + '?' + '?\r\n';
2264       }
2265     }
2266     rc     += ']\r\n';
2267   } else {
2268     rc     += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2269   }
2270   return rc;
2271 };
2272
2273 VT100.prototype.checkComposedKeys = function(event) {
2274   // Composed keys (at least on Linux) do not generate normal events.
2275   // Instead, they get entered into the text field. We normally catch
2276   // this on the next keyup event.
2277   var s              = this.input.value;
2278   if (s.length) {
2279     this.input.value = '';
2280     if (this.menu.style.visibility == 'hidden') {
2281       this.keysPressed(s);
2282     }
2283   }
2284 };
2285
2286 VT100.prototype.fixEvent = function(event) {
2287   // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2288   // is used as a second-level selector, clear the modifier bits before
2289   // handling the event.
2290   if (event.ctrlKey && event.altKey) {
2291     var fake                = [ ];
2292     fake.charCode           = event.charCode;
2293     fake.keyCode            = event.keyCode;
2294     fake.ctrlKey            = false;
2295     fake.shiftKey           = event.shiftKey;
2296     fake.altKey             = false;
2297     fake.metaKey            = event.metaKey;
2298     return fake;
2299   }
2300
2301   // Some browsers fail to translate keys, if both shift and alt/meta is
2302   // pressed at the same time. We try to translate those cases, but that
2303   // only works for US keyboard layouts.
2304   if (event.shiftKey) {
2305     var u                   = undefined;
2306     var s                   = undefined;
2307     switch (this.lastNormalKeyDownEvent.keyCode) {
2308     case  39: /* ' -> " */ u = 39; s =  34; break;
2309     case  44: /* , -> < */ u = 44; s =  60; break;
2310     case  45: /* - -> _ */ u = 45; s =  95; break;
2311     case  46: /* . -> > */ u = 46; s =  62; break;
2312     case  47: /* / -> ? */ u = 47; s =  63; break;
2313
2314     case  48: /* 0 -> ) */ u = 48; s =  41; break;
2315     case  49: /* 1 -> ! */ u = 49; s =  33; break;
2316     case  50: /* 2 -> @ */ u = 50; s =  64; break;
2317     case  51: /* 3 -> # */ u = 51; s =  35; break;
2318     case  52: /* 4 -> $ */ u = 52; s =  36; break;
2319     case  53: /* 5 -> % */ u = 53; s =  37; break;
2320     case  54: /* 6 -> ^ */ u = 54; s =  94; break;
2321     case  55: /* 7 -> & */ u = 55; s =  38; break;
2322     case  56: /* 8 -> * */ u = 56; s =  42; break;
2323     case  57: /* 9 -> ( */ u = 57; s =  40; break;
2324
2325     case  59: /* ; -> : */ u = 59; s =  58; break;
2326     case  61: /* = -> + */ u = 61; s =  43; break;
2327     case  91: /* [ -> { */ u = 91; s = 123; break;
2328     case  92: /* \ -> | */ u = 92; s = 124; break;
2329     case  93: /* ] -> } */ u = 93; s = 125; break; 
2330     case  96: /* ` -> ~ */ u = 96; s = 126; break;
2331
2332     case 109: /* - -> _ */ u = 45; s =  95; break;
2333     case 111: /* / -> ? */ u = 47; s =  63; break;
2334
2335     case 186: /* ; -> : */ u = 59; s =  58; break;
2336     case 187: /* = -> + */ u = 61; s =  43; break;
2337     case 188: /* , -> < */ u = 44; s =  60; break;
2338     case 189: /* - -> _ */ u = 45; s =  95; break;
2339     case 190: /* . -> > */ u = 46; s =  62; break;
2340     case 191: /* / -> ? */ u = 47; s =  63; break;
2341     case 192: /* ` -> ~ */ u = 96; s = 126; break;
2342     case 219: /* [ -> { */ u = 91; s = 123; break;
2343     case 220: /* \ -> | */ u = 92; s = 124; break;
2344     case 221: /* ] -> } */ u = 93; s = 125; break; 
2345     case 222: /* ' -> " */ u = 39; s =  34; break;
2346     default:                                break;
2347     }
2348     if (s && (event.charCode == u || event.charCode == 0)) {
2349       var fake              = [ ];
2350       fake.charCode         = s;
2351       fake.keyCode          = event.keyCode;
2352       fake.ctrlKey          = event.ctrlKey;
2353       fake.shiftKey         = event.shiftKey;
2354       fake.altKey           = event.altKey;
2355       fake.metaKey          = event.metaKey;
2356       return fake;
2357     }
2358   }
2359   return event;
2360 };
2361
2362 VT100.prototype.keyDown = function(event) {
2363   // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2364   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2365   //             event.metaKey ? ', ' +
2366   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2367   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2368   //            '\r\n');
2369   this.checkComposedKeys(event);
2370   this.lastKeyPressedEvent      = undefined;
2371   this.lastKeyDownEvent         = undefined;
2372   this.lastNormalKeyDownEvent   = event;
2373
2374   var asciiKey                  =
2375     event.keyCode ==  32                         ||
2376     event.keyCode >=  48 && event.keyCode <=  57 ||
2377     event.keyCode >=  65 && event.keyCode <=  90;
2378   var alphNumKey                =
2379     asciiKey                                     ||
2380     event.keyCode >=  96 && event.keyCode <= 105 ||
2381     event.keyCode == 226;
2382   var normalKey                 =
2383     alphNumKey                                   ||
2384     event.keyCode ==  59 || event.keyCode ==  61 ||
2385     event.keyCode == 106 || event.keyCode == 107 ||
2386     event.keyCode >= 109 && event.keyCode <= 111 ||
2387     event.keyCode >= 186 && event.keyCode <= 192 ||
2388     event.keyCode >= 219 && event.keyCode <= 222 ||
2389     event.keyCode == 252;
2390   try {
2391     if (navigator.appName == 'Konqueror') {
2392       normalKey                |= event.keyCode < 128;
2393     }
2394   } catch (e) {
2395   }
2396
2397   // We normally prefer to look at keypress events, as they perform the
2398   // translation from keyCode to charCode. This is important, as the
2399   // translation is locale-dependent.
2400   // But for some keys, we must intercept them during the keydown event,
2401   // as they would otherwise get interpreted by the browser.
2402   // Even, when doing all of this, there are some keys that we can never
2403   // intercept. This applies to some of the menu navigation keys in IE.
2404   // In fact, we see them, but we cannot stop IE from seeing them, too.
2405   if ((event.charCode || event.keyCode) &&
2406       ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2407         !event.shiftKey &&
2408         // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2409         // interpret this sequence ourselves, as some keyboard layouts use
2410         // it for second-level layouts.
2411         !(event.ctrlKey && event.altKey)) ||
2412        this.catchModifiersEarly && normalKey && !alphNumKey &&
2413        (event.ctrlKey || event.altKey || event.metaKey) ||
2414        !normalKey)) {
2415     this.lastKeyDownEvent       = event;
2416     var fake                    = [ ];
2417     fake.ctrlKey                = event.ctrlKey;
2418     fake.shiftKey               = event.shiftKey;
2419     fake.altKey                 = event.altKey;
2420     fake.metaKey                = event.metaKey;
2421     if (asciiKey) {
2422       fake.charCode             = event.keyCode;
2423       fake.keyCode              = 0;
2424     } else {
2425       fake.charCode             = 0;
2426       fake.keyCode              = event.keyCode;
2427       if (!alphNumKey && event.shiftKey) {
2428         fake                    = this.fixEvent(fake);
2429       }
2430     }
2431
2432     this.handleKey(fake);
2433     this.lastNormalKeyDownEvent = undefined;
2434
2435     try {
2436       // For non-IE browsers
2437       event.stopPropagation();
2438       event.preventDefault();
2439     } catch (e) {
2440     }
2441     try {
2442       // For IE
2443       event.cancelBubble = true;
2444       event.returnValue  = false;
2445       event.keyCode      = 0;
2446     } catch (e) {
2447     }
2448
2449     return false;
2450   }
2451   return true;
2452 };
2453
2454 VT100.prototype.keyPressed = function(event) {
2455   // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2456   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2457   //             event.metaKey ? ', ' +
2458   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2459   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2460   //            '\r\n');
2461   if (this.lastKeyDownEvent) {
2462     // If we already processed the key on keydown, do not process it
2463     // again here. Ideally, the browser should not even have generated a
2464     // keypress event in this case. But that does not appear to always work.
2465     this.lastKeyDownEvent     = undefined;
2466   } else {
2467     this.handleKey(event.altKey || event.metaKey
2468                    ? this.fixEvent(event) : event);
2469   }
2470
2471   try {
2472     // For non-IE browsers
2473     event.preventDefault();
2474   } catch (e) {
2475   }
2476
2477   try {
2478     // For IE
2479     event.cancelBubble = true;
2480     event.returnValue  = false;
2481     event.keyCode      = 0;
2482   } catch (e) {
2483   }
2484
2485   this.lastNormalKeyDownEvent = undefined;
2486   this.lastKeyPressedEvent    = event;
2487   return false;
2488 };
2489
2490 VT100.prototype.keyUp = function(event) {
2491   // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2492   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2493   //             event.metaKey ? ', ' +
2494   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2495   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2496   //            '\r\n');
2497   if (this.lastKeyPressedEvent) {
2498     // The compose key on Linux occasionally confuses the browser and keeps
2499     // inserting bogus characters into the input field, even if just a regular
2500     // key has been pressed. Detect this case and drop the bogus characters.
2501     (event.target ||
2502      event.srcElement).value      = '';
2503   } else {
2504     // This is usually were we notice that a key has been composed and
2505     // thus failed to generate normal events.
2506     this.checkComposedKeys(event);
2507
2508     // Some browsers don't report keypress events if ctrl or alt is pressed
2509     // for non-alphanumerical keys. Patch things up for now, but in the
2510     // future we will catch these keys earlier (in the keydown handler).
2511     if (this.lastNormalKeyDownEvent) {
2512       // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
2513       this.catchModifiersEarly    = true;
2514       var asciiKey                =
2515         event.keyCode ==  32                         ||
2516         event.keyCode >=  48 && event.keyCode <=  57 ||
2517         event.keyCode >=  65 && event.keyCode <=  90;
2518       var alphNumKey              =
2519         asciiKey                                     ||
2520         event.keyCode >=  96 && event.keyCode <= 105;
2521       var normalKey               =
2522         alphNumKey                                   ||
2523         event.keyCode ==  59 || event.keyCode ==  61 ||
2524         event.keyCode == 106 || event.keyCode == 107 ||
2525         event.keyCode >= 109 && event.keyCode <= 111 ||
2526         event.keyCode >= 186 && event.keyCode <= 192 ||
2527         event.keyCode >= 219 && event.keyCode <= 222 ||
2528         event.keyCode == 252;
2529       var fake                    = [ ];
2530       fake.ctrlKey                = event.ctrlKey;
2531       fake.shiftKey               = event.shiftKey;
2532       fake.altKey                 = event.altKey;
2533       fake.metaKey                = event.metaKey;
2534       if (asciiKey) {
2535         fake.charCode             = event.keyCode;
2536         fake.keyCode              = 0;
2537       } else {
2538         fake.charCode             = 0;
2539         fake.keyCode              = event.keyCode;
2540         if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2541           fake                    = this.fixEvent(fake);
2542         }
2543       }
2544       this.lastNormalKeyDownEvent = undefined;
2545       this.handleKey(fake);
2546     }
2547   }
2548
2549   try {
2550     // For IE
2551     event.cancelBubble            = true;
2552     event.returnValue             = false;
2553     event.keyCode                 = 0;
2554   } catch (e) {
2555   }
2556
2557   this.lastKeyDownEvent           = undefined;
2558   this.lastKeyPressedEvent        = undefined;
2559   return false;
2560 };
2561
2562 VT100.prototype.animateCursor = function(inactive) {
2563   if (!this.cursorInterval) {
2564     this.cursorInterval     = setInterval(
2565       function(vt100) {
2566         return function() {
2567           vt100.animateCursor();
2568
2569           // Use this opportunity to check whether the user entered a composed
2570           // key, or whether somebody pasted text into the textfield.
2571           vt100.checkComposedKeys();
2572         }
2573       }(this), 500);
2574   }
2575   if (inactive != undefined || this.cursor.className != 'inactive') {
2576     if (inactive) {
2577       this.cursor.className = 'inactive';
2578     } else {
2579       this.cursor.className = this.cursor.className == 'bright'
2580                               ? 'dim' : 'bright';
2581     }
2582   }
2583 };
2584
2585 VT100.prototype.blurCursor = function() {
2586   this.animateCursor(true);
2587 };
2588
2589 VT100.prototype.focusCursor = function() {
2590   this.animateCursor(false);
2591 };
2592
2593 VT100.prototype.flashScreen = function() {
2594   this.isInverted       = !this.isInverted;
2595   this.refreshInvertedState();
2596   this.isInverted       = !this.isInverted;
2597   setTimeout(function(vt100) {
2598                return function() {
2599                  vt100.refreshInvertedState();
2600                };
2601              }(this), 100);
2602 };
2603
2604 VT100.prototype.beep = function() {
2605   if (this.visualBell) {
2606     this.flashScreen();
2607   } else {
2608     try {
2609       this.beeper.Play();
2610     } catch (e) {
2611       try {
2612         this.beeper.src = 'beep.wav';
2613       } catch (e) {
2614       }
2615     }
2616   }
2617 };
2618
2619 VT100.prototype.bs = function() {
2620   if (this.cursorX > 0) {
2621     this.gotoXY(this.cursorX - 1, this.cursorY);
2622     this.needWrap = false;
2623   }
2624 };
2625
2626 VT100.prototype.ht = function(count) {
2627   if (count == undefined) {
2628     count        = 1;
2629   }
2630   var cx         = this.cursorX;
2631   while (count-- > 0) {
2632     while (cx++ < this.terminalWidth) {
2633       var tabState = this.userTabStop[cx];
2634       if (tabState == false) {
2635         // Explicitly cleared tab stop
2636         continue;
2637       } else if (tabState) {
2638         // Explicitly set tab stop
2639         break;
2640       } else {
2641         // Default tab stop at each eighth column
2642         if (cx % 8 == 0) {
2643           break;
2644         }
2645       }
2646     }
2647   }
2648   if (cx > this.terminalWidth - 1) {
2649     cx           = this.terminalWidth - 1;
2650   }
2651   if (cx != this.cursorX) {
2652     this.gotoXY(cx, this.cursorY);
2653   }
2654 };
2655
2656 VT100.prototype.rt = function(count) {
2657   if (count == undefined) {
2658     count          = 1 ;
2659   }
2660   var cx           = this.cursorX;
2661   while (count-- > 0) {
2662     while (cx-- > 0) {
2663       var tabState = this.userTabStop[cx];
2664       if (tabState == false) {
2665         // Explicitly cleared tab stop
2666         continue;
2667       } else if (tabState) {
2668         // Explicitly set tab stop
2669         break;
2670       } else {
2671         // Default tab stop at each eighth column
2672         if (cx % 8 == 0) {
2673           break;
2674         }
2675       }
2676     }
2677   }
2678   if (cx < 0) {
2679     cx             = 0;
2680   }
2681   if (cx != this.cursorX) {
2682     this.gotoXY(cx, this.cursorY);
2683   }
2684 };
2685
2686 VT100.prototype.cr = function() {
2687   this.gotoXY(0, this.cursorY);
2688   this.needWrap = false;
2689 };
2690
2691 VT100.prototype.lf = function(count) {
2692   if (count == undefined) {
2693     count    = 1;
2694   } else {
2695     if (count > this.terminalHeight) {
2696       count  = this.terminalHeight;
2697     }
2698     if (count < 1) {
2699       count  = 1;
2700     }
2701   }
2702   while (count-- > 0) {
2703     if (this.cursorY == this.bottom - 1) {
2704       this.scrollRegion(0, this.top + 1,
2705                         this.terminalWidth, this.bottom - this.top - 1,
2706                         0, -1, this.color, this.style);
2707       offset = undefined;
2708     } else if (this.cursorY < this.terminalHeight - 1) {
2709       this.gotoXY(this.cursorX, this.cursorY + 1);
2710     }
2711   }
2712 };
2713
2714 VT100.prototype.ri = function(count) {
2715   if (count == undefined) {
2716     count   = 1;
2717   } else {
2718     if (count > this.terminalHeight) {
2719       count = this.terminalHeight;
2720     }
2721     if (count < 1) {
2722       count = 1;
2723     }
2724   }
2725   while (count-- > 0) {
2726     if (this.cursorY == this.top) {
2727       this.scrollRegion(0, this.top,
2728                         this.terminalWidth, this.bottom - this.top - 1,
2729                         0, 1, this.color, this.style);
2730     } else if (this.cursorY > 0) {
2731       this.gotoXY(this.cursorX, this.cursorY - 1);
2732     }
2733   }
2734   this.needWrap = false;
2735 };
2736
2737 VT100.prototype.respondID = function() {
2738   this.respondString += '\u001B[?6c';
2739 };
2740
2741 VT100.prototype.respondSecondaryDA = function() {
2742   this.respondString += '\u001B[>0;0;0c';
2743 };
2744
2745
2746 VT100.prototype.updateStyle = function() {
2747   this.style   = '';
2748   if (this.attr & ATTR_UNDERLINE) {
2749     this.style = 'text-decoration:underline;';
2750   }
2751   var bg       = (this.attr >> 4) & 0xF;
2752   var fg       =  this.attr       & 0xF;
2753   if (this.attr & ATTR_REVERSE) {
2754     var tmp    = bg;
2755     bg         = fg;
2756     fg         = tmp;
2757   }
2758   if ((this.attr & (ATTR_REVERSE | ATTR_DIM)) == ATTR_DIM) {
2759     fg         = 8; // Dark grey
2760   } else if (this.attr & ATTR_BRIGHT) {
2761     fg        |= 8;
2762   }
2763   if (this.attr & ATTR_BLINK) {
2764     bg        ^= 8;
2765   }
2766   // Make some readability enhancements. Most notably, disallow identical
2767   // background and foreground colors.
2768   if (bg == fg) {
2769     if ((fg   ^= 8) == 7) {
2770       fg       = 8;
2771     }
2772   }
2773   // And disallow bright colors on a light-grey background.
2774   if (bg == 7 && fg >= 8) {
2775     if ((fg   -= 8) == 7) {
2776       fg       = 8;
2777     }
2778   }
2779
2780   this.color   = 'ansi' + fg + ' bgAnsi' + bg;
2781 };
2782
2783 VT100.prototype.setAttrColors = function(attr) {
2784   if (attr != this.attr) {
2785     this.attr = attr;
2786     this.updateStyle();
2787   }
2788 };
2789
2790 VT100.prototype.saveCursor = function() {
2791   this.savedX[this.currentScreen]     = this.cursorX;
2792   this.savedY[this.currentScreen]     = this.cursorY;
2793   this.savedAttr[this.currentScreen]  = this.attr;
2794   this.savedUseGMap                   = this.useGMap;
2795   for (var i = 0; i < 4; i++) {
2796     this.savedGMap[i]                 = this.GMap[i];
2797   }
2798   this.savedValid[this.currentScreen] = true;
2799 };
2800
2801 VT100.prototype.restoreCursor = function() {
2802   if (!this.savedValid[this.currentScreen]) {
2803     return;
2804   }
2805   this.attr      = this.savedAttr[this.currentScreen];
2806   this.updateStyle();
2807   this.useGMap   = this.savedUseGMap;
2808   for (var i = 0; i < 4; i++) {
2809     this.GMap[i] = this.savedGMap[i];
2810   }
2811   this.translate = this.GMap[this.useGMap];
2812   this.needWrap  = false;
2813   this.gotoXY(this.savedX[this.currentScreen],
2814               this.savedY[this.currentScreen]);
2815 };
2816
2817 VT100.prototype.setMode = function(state) {
2818   for (var i = 0; i <= this.npar; i++) {
2819     if (this.isQuestionMark) {
2820       switch (this.par[i]) {
2821       case  1: this.cursorKeyMode      = state;                      break;
2822       case  3: /* Toggling between 80/132 mode is not implemented */ break;
2823       case  5: this.isInverted = state; this.refreshInvertedState(); break;
2824       case  6: this.offsetMode         = state;                      break;
2825       case  7: this.autoWrapMode       = state;                      break;
2826       case 1000:
2827       case  9: this.mouseReporting     = state;                      break;
2828       case 25: this.cursorNeedsShowing = state;
2829                if (state) { this.showCursor(); }
2830                else       { this.hideCursor(); }                     break;
2831       case 1047:
2832       case 1049:
2833       case 47: this.enableAlternateScreen(state);                    break;
2834       default:                                                       break;
2835       }
2836     } else {
2837       switch (this.par[i]) {
2838       case  3: this.dispCtrl           = state;                      break;
2839       case  4: this.insertMode         = state;                      break;
2840       case  20:this.crLfMode           = state;                      break;
2841       default:                                                       break;
2842       }
2843     }
2844   }
2845 };
2846
2847 VT100.prototype.statusReport = function() {
2848   // Ready and operational.
2849   this.respondString += '\u001B[0n';
2850 };
2851
2852 VT100.prototype.cursorReport = function() {
2853   this.respondString += '\u001B[' +
2854                         (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2855                         ';' +
2856                         (this.cursorX + 1) +
2857                         'R';
2858 };
2859
2860 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2861   // Changing of cursor color is not implemented.
2862 };
2863
2864 VT100.prototype.openPrinterWindow = function() {
2865   var rc            = true;
2866   try {
2867     if (!this.printWin || this.printWin.closed) {
2868       this.printWin = window.open('', 'print-output',
2869         'width=800,height=600,directories=no,location=no,menubar=yes,' +
2870         'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
2871       this.printWin.document.body.innerHTML =
2872         '<link rel="stylesheet" href="' +
2873           document.location.protocol + '//' + document.location.host +
2874           document.location.pathname.replace(/[^/]*$/, '') +
2875           'print-styles.css" type="text/css">\n' +
2876         '<div id="options"><input id="autoprint" type="checkbox"' +
2877           (this.autoprint ? ' checked' : '') + '>' +
2878           'Automatically, print page(s) when job is ready' +
2879         '</input></div>\n' +
2880         '<div id="spacer"><input type="checkbox">&nbsp;</input></div>' +
2881         '<pre id="print"></pre>\n';
2882       var autoprint = this.printWin.document.getElementById('autoprint');
2883       this.addListener(autoprint, 'click',
2884                        (function(vt100, autoprint) {
2885                          return function() {
2886                            vt100.autoprint = autoprint.checked;
2887                            vt100.storeUserSettings();
2888                            return false;
2889                          };
2890                        })(this, autoprint));
2891       this.printWin.document.title = 'ShellInABox Printer Output';
2892     }
2893   } catch (e) {
2894     // Maybe, a popup blocker prevented us from working. Better catch the
2895     // exception, so that we won't break the entire terminal session. The
2896     // user probably needs to disable the blocker first before retrying the
2897     // operation.
2898     rc              = false;
2899   }
2900   rc               &= this.printWin && !this.printWin.closed &&
2901                       (this.printWin.innerWidth ||
2902                        this.printWin.document.documentElement.clientWidth ||
2903                        this.printWin.document.body.clientWidth) > 1;
2904
2905   if (!rc && this.printing == 100) {
2906     // Different popup blockers work differently. We try to detect a couple
2907     // of common methods. And then we retry again a brief amount later, as
2908     // false positives are otherwise possible. If we are sure that there is
2909     // a popup blocker in effect, we alert the user to it. This is helpful
2910     // as some popup blockers have minimal or no UI, and the user might not
2911     // notice that they are missing the popup. In any case, we only show at
2912     // most one message per print job.
2913     this.printing   = true;
2914     setTimeout((function(win) {
2915                   return function() {
2916                     if (!win || win.closed ||
2917                         (win.innerWidth ||
2918                          win.document.documentElement.clientWidth ||
2919                          win.document.body.clientWidth) <= 1) {
2920                       alert('Attempted to print, but a popup blocker ' +
2921                             'prevented the printer window from opening');
2922                     }
2923                   };
2924                 })(this.printWin), 2000);
2925   }
2926   return rc;
2927 };
2928
2929 VT100.prototype.sendToPrinter = function(s) {
2930   this.openPrinterWindow();
2931   try {
2932     var doc   = this.printWin.document;
2933     var print = doc.getElementById('print');
2934     if (print.lastChild && print.lastChild.nodeName == '#text') {
2935       print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
2936     } else {
2937       print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
2938     }
2939   } catch (e) {
2940     // There probably was a more aggressive popup blocker that prevented us
2941     // from accessing the printer windows.
2942   }
2943 };
2944
2945 VT100.prototype.sendControlToPrinter = function(ch) {
2946   // We get called whenever doControl() is active. But for the printer, we
2947   // only implement a basic line printer that doesn't understand most of
2948   // the escape sequences of the VT100 terminal. In fact, the only escape
2949   // sequence that we really need to recognize is '^[[5i' for turning the
2950   // printer off.
2951   try {
2952     switch (ch) {
2953     case  9:
2954       // HT
2955       this.openPrinterWindow();
2956       var doc                 = this.printWin.document;
2957       var print               = doc.getElementById('print');
2958       var chars               = print.lastChild &&
2959                                 print.lastChild.nodeName == '#text' ?
2960                                 print.lastChild.textContent.length : 0;
2961       this.sendToPrinter(this.spaces(8 - (chars % 8)));
2962       break;
2963     case 10:
2964       // CR
2965       break;
2966     case 12:
2967       // FF
2968       this.openPrinterWindow();
2969       var pageBreak           = this.printWin.document.createElement('div');
2970       pageBreak.className     = 'pagebreak';
2971       pageBreak.innerHTML     = '<hr />';
2972       this.printWin.document.getElementById('print').appendChild(pageBreak);
2973       break;
2974     case 13:
2975       // LF
2976       this.openPrinterWindow();
2977       var lineBreak           = this.printWin.document.createElement('br');
2978       this.printWin.document.getElementById('print').appendChild(lineBreak);
2979       break;
2980     case 27:
2981       // ESC
2982       this.isEsc              = ESesc;
2983       break;
2984     default:
2985       switch (this.isEsc) {
2986       case ESesc:
2987         this.isEsc            = ESnormal;
2988         switch (ch) {
2989         case 0x5B /*[*/:
2990           this.isEsc          = ESsquare;
2991           break;
2992         default:
2993           break;
2994         }
2995         break;
2996       case ESsquare:
2997         this.npar             = 0;
2998         this.par              = [ 0, 0, 0, 0, 0, 0, 0, 0,
2999                                   0, 0, 0, 0, 0, 0, 0, 0 ];
3000         this.isEsc            = ESgetpars;
3001         this.isQuestionMark   = ch == 0x3F /*?*/;
3002         if (this.isQuestionMark) {
3003           break;
3004         }
3005         // Fall through
3006       case ESgetpars: 
3007         if (ch == 0x3B /*;*/) {
3008           this.npar++;
3009           break;
3010         } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3011           var par             = this.par[this.npar];
3012           if (par == undefined) {
3013             par               = 0;
3014           }
3015           this.par[this.npar] = 10*par + (ch & 0xF);
3016           break;
3017         } else {
3018           this.isEsc          = ESgotpars;
3019         }
3020         // Fall through
3021       case ESgotpars:
3022         this.isEsc            = ESnormal;
3023         if (this.isQuestionMark) {
3024           break;
3025         }
3026         switch (ch) {
3027         case 0x69 /*i*/:
3028           this.csii(this.par[0]);
3029           break;
3030         default:
3031           break;
3032         }
3033         break;
3034       default:
3035         this.isEsc            = ESnormal;
3036         break;
3037       }
3038       break;
3039     }
3040   } catch (e) {
3041     // There probably was a more aggressive popup blocker that prevented us
3042     // from accessing the printer windows.
3043   }
3044 };
3045
3046 VT100.prototype.csiAt = function(number) {
3047   // Insert spaces
3048   if (number == 0) {
3049     number      = 1;
3050   }
3051   if (number > this.terminalWidth - this.cursorX) {
3052     number      = this.terminalWidth - this.cursorX;
3053   }
3054   this.scrollRegion(this.cursorX, this.cursorY,
3055                     this.terminalWidth - this.cursorX - number, 1,
3056                     number, 0, this.color, this.style);
3057   this.needWrap = false;
3058 };
3059
3060 VT100.prototype.csii = function(number) {
3061   // Printer control
3062   switch (number) {
3063   case 0: // Print Screen
3064     window.print();
3065     break;
3066   case 4: // Stop printing
3067     try {
3068       if (this.printing && this.printWin && !this.printWin.closed) {
3069         var print = this.printWin.document.getElementById('print');
3070         while (print.lastChild &&
3071                print.lastChild.tagName == 'DIV' &&
3072                print.lastChild.className == 'pagebreak') {
3073           // Remove trailing blank pages
3074           print.removeChild(print.lastChild);
3075         }
3076         if (this.autoprint) {
3077           this.printWin.print();
3078         }
3079       }
3080     } catch (e) {
3081     }
3082     this.printing = false;
3083     break;
3084   case 5: // Start printing
3085     if (!this.printing && this.printWin && !this.printWin.closed) {
3086       this.printWin.document.getElementById('print').innerHTML = '';
3087     }
3088     this.printing = 100;
3089     break;
3090   default:
3091     break;
3092   }
3093 };
3094
3095 VT100.prototype.csiJ = function(number) {
3096   switch (number) {
3097   case 0: // Erase from cursor to end of display
3098     this.clearRegion(this.cursorX, this.cursorY,
3099                      this.terminalWidth - this.cursorX, 1,
3100                      this.color, this.style);
3101     if (this.cursorY < this.terminalHeight-2) {
3102       this.clearRegion(0, this.cursorY+1,
3103                        this.terminalWidth, this.terminalHeight-this.cursorY-1,
3104                        this.color, this.style);
3105     }
3106     break;
3107   case 1: // Erase from start to cursor
3108     if (this.cursorY > 0) {
3109       this.clearRegion(0, 0,
3110                        this.terminalWidth, this.cursorY,
3111                        this.color, this.style);
3112     }
3113     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3114                      this.color, this.style);
3115     break;
3116   case 2: // Erase whole display
3117     this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3118                      this.color, this.style);
3119     break;
3120   default:
3121     return;
3122   }
3123   needWrap = false;
3124 };
3125
3126 VT100.prototype.csiK = function(number) {
3127   switch (number) {
3128   case 0: // Erase from cursor to end of line
3129     this.clearRegion(this.cursorX, this.cursorY,
3130                      this.terminalWidth - this.cursorX, 1,
3131                      this.color, this.style);
3132     break;
3133   case 1: // Erase from start of line to cursor
3134     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3135                      this.color, this.style);
3136     break;
3137   case 2: // Erase whole line
3138     this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3139                      this.color, this.style);
3140     break;
3141   default:
3142     return;
3143   }
3144   needWrap = false;
3145 };
3146
3147 VT100.prototype.csiL = function(number) {
3148   // Open line by inserting blank line(s)
3149   if (this.cursorY >= this.bottom) {
3150     return;
3151   }
3152   if (number == 0) {
3153     number = 1;
3154   }
3155   if (number > this.bottom - this.cursorY) {
3156     number = this.bottom - this.cursorY;
3157   }
3158   this.scrollRegion(0, this.cursorY,
3159                     this.terminalWidth, this.bottom - this.cursorY - number,
3160                     0, number, this.color, this.style);
3161   needWrap = false;
3162 };
3163
3164 VT100.prototype.csiM = function(number) {
3165   // Delete line(s), scrolling up the bottom of the screen.
3166   if (this.cursorY >= this.bottom) {
3167     return;
3168   }
3169   if (number == 0) {
3170     number = 1;
3171   }
3172   if (number > this.bottom - this.cursorY) {
3173     number = bottom - cursorY;
3174   }
3175   this.scrollRegion(0, this.cursorY + number,
3176                     this.terminalWidth, this.bottom - this.cursorY - number,
3177                     0, -number, this.color, this.style);
3178   needWrap = false;
3179 };
3180
3181 VT100.prototype.csim = function() {
3182   for (var i = 0; i <= this.npar; i++) {
3183     switch (this.par[i]) {
3184     case 0:  this.attr  = ATTR_DEFAULT;                                break;
3185     case 1:  this.attr  = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT;         break;
3186     case 2:  this.attr  = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM;         break;
3187     case 4:  this.attr |= ATTR_UNDERLINE;                              break;
3188     case 5:  this.attr |= ATTR_BLINK;                                  break;
3189     case 7:  this.attr |= ATTR_REVERSE;                                break;
3190     case 10:
3191       this.translate    = this.GMap[this.useGMap];
3192       this.dispCtrl     = false;
3193       this.toggleMeta   = false;
3194       break;
3195     case 11:
3196       this.translate    = this.CodePage437Map;
3197       this.dispCtrl     = true;
3198       this.toggleMeta   = false;
3199       break;
3200     case 12:
3201       this.translate    = this.CodePage437Map;
3202       this.dispCtrl     = true;
3203       this.toggleMeta   = true;
3204       break;
3205     case 21:
3206     case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM);                     break;
3207     case 24: this.attr &= ~ ATTR_UNDERLINE;                            break;
3208     case 25: this.attr &= ~ ATTR_BLINK;                                break;
3209     case 27: this.attr &= ~ ATTR_REVERSE;                              break;
3210     case 38: this.attr  = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|
3211                           ATTR_UNDERLINE;                              break;
3212     case 39: this.attr &= ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F); break;
3213     case 49: this.attr |= 0xF0;                                        break;
3214     default:
3215       if (this.par[i] >= 30 && this.par[i] <= 37) {
3216           var fg        = this.par[i] - 30;
3217           this.attr     = (this.attr & ~0x0F) | fg;
3218       } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3219           var bg        = this.par[i] - 40;
3220           this.attr     = (this.attr & ~0xF0) | (bg << 4);
3221       }
3222       break;
3223     }
3224   }
3225   this.updateStyle();
3226 };
3227
3228 VT100.prototype.csiP = function(number) {
3229   // Delete character(s) following cursor
3230   if (number == 0) {
3231     number = 1;
3232   }
3233   if (number > this.terminalWidth - this.cursorX) {
3234     number = this.terminalWidth - this.cursorX;
3235   }
3236   this.scrollRegion(this.cursorX + number, this.cursorY,
3237                     this.terminalWidth - this.cursorX - number, 1,
3238                     -number, 0, this.color, this.style);
3239   needWrap = false;
3240 };
3241
3242 VT100.prototype.csiX = function(number) {
3243   // Clear characters following cursor
3244   if (number == 0) {
3245     number++;
3246   }
3247   if (number > this.terminalWidth - this.cursorX) {
3248     number = this.terminalWidth - this.cursorX;
3249   }
3250   this.clearRegion(this.cursorX, this.cursorY, number, 1,
3251                    this.color, this.style);
3252   needWrap = false;
3253 };
3254
3255 VT100.prototype.settermCommand = function() {
3256   // Setterm commands are not implemented
3257 };
3258
3259 VT100.prototype.doControl = function(ch) {
3260   if (this.printing) {
3261     this.sendControlToPrinter(ch);
3262     return '';
3263   }
3264   var lineBuf                = '';
3265   switch (ch) {
3266   case 0x00: /* ignored */                                              break;
3267   case 0x08: this.bs();                                                 break;
3268   case 0x09: this.ht();                                                 break;
3269   case 0x0A:
3270   case 0x0B:
3271   case 0x0C:
3272   case 0x84: this.lf(); if (!this.crLfMode)                             break;
3273   case 0x0D: this.cr();                                                 break;
3274   case 0x85: this.cr(); this.lf();                                      break;
3275   case 0x0E: this.useGMap     = 1;
3276              this.translate   = this.GMap[1];
3277              this.dispCtrl    = true;                                   break;
3278   case 0x0F: this.useGMap     = 0;
3279              this.translate   = this.GMap[0];
3280              this.dispCtrl    = false;                                  break;
3281   case 0x18:
3282   case 0x1A: this.isEsc       = ESnormal;                               break;
3283   case 0x1B: this.isEsc       = ESesc;                                  break;
3284   case 0x7F: /* ignored */                                              break;
3285   case 0x88: this.userTabStop[this.cursorX] = true;                     break;
3286   case 0x8D: this.ri();                                                 break;
3287   case 0x8E: this.isEsc       = ESss2;                                  break;
3288   case 0x8F: this.isEsc       = ESss3;                                  break;
3289   case 0x9A: this.respondID();                                          break;
3290   case 0x9B: this.isEsc       = ESsquare;                               break;
3291   case 0x07: if (this.isEsc != ESstatus) {
3292                this.beep();                                             break;
3293              }
3294              /* fall thru */
3295   default:   switch (this.isEsc) {
3296     case ESesc:
3297       this.isEsc              = ESnormal;
3298       switch (ch) {
3299 /*%*/ case 0x25: this.isEsc   = ESpercent;                              break;
3300 /*(*/ case 0x28: this.isEsc   = ESsetG0;                                break;
3301 /*-*/ case 0x2D:
3302 /*)*/ case 0x29: this.isEsc   = ESsetG1;                                break;
3303 /*.*/ case 0x2E:
3304 /***/ case 0x2A: this.isEsc   = ESsetG2;                                break;
3305 /*/*/ case 0x2F:
3306 /*+*/ case 0x2B: this.isEsc   = ESsetG3;                                break;
3307 /*#*/ case 0x23: this.isEsc   = EShash;                                 break;
3308 /*7*/ case 0x37: this.saveCursor();                                     break;
3309 /*8*/ case 0x38: this.restoreCursor();                                  break;
3310 /*>*/ case 0x3E: this.applKeyMode = false;                              break;
3311 /*=*/ case 0x3D: this.applKeyMode = true;                               break;
3312 /*D*/ case 0x44: this.lf();                                             break;
3313 /*E*/ case 0x45: this.cr(); this.lf();                                  break;
3314 /*M*/ case 0x4D: this.ri();                                             break;
3315 /*N*/ case 0x4E: this.isEsc   = ESss2;                                  break;
3316 /*O*/ case 0x4F: this.isEsc   = ESss3;                                  break;
3317 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true;                 break;
3318 /*Z*/ case 0x5A: this.respondID();                                      break;
3319 /*[*/ case 0x5B: this.isEsc   = ESsquare;                               break;
3320 /*]*/ case 0x5D: this.isEsc   = ESnonstd;                               break;
3321 /*c*/ case 0x63: this.reset();                                          break;
3322 /*g*/ case 0x67: this.flashScreen();                                    break;
3323       default:                                                          break;
3324       }
3325       break;
3326     case ESnonstd:
3327       switch (ch) {
3328 /*0*/ case 0x30:
3329 /*1*/ case 0x31:
3330 /*2*/ case 0x32: this.statusString = ''; this.isEsc  = ESstatus;        break;
3331 /*P*/ case 0x50: this.npar    = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3332                  this.isEsc   = ESpalette;                              break;
3333 /*R*/ case 0x52: // Palette support is not implemented
3334                  this.isEsc   = ESnormal;                               break;
3335       default:   this.isEsc   = ESnormal;                               break;
3336       }
3337       break;
3338     case ESpalette:
3339       if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3340           (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3341           (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3342         this.par[this.npar++] = ch > 0x39  /*9*/ ? (ch & 0xDF) - 55
3343                                                 : (ch & 0xF);
3344         if (this.npar == 7) {
3345           // Palette support is not implemented
3346           this.isEsc          = ESnormal;
3347         }
3348       } else {
3349         this.isEsc            = ESnormal;
3350       }
3351       break;
3352     case ESsquare:
3353       this.npar               = 0;
3354       this.par                = [ 0, 0, 0, 0, 0, 0, 0, 0,
3355                                   0, 0, 0, 0, 0, 0, 0, 0 ];
3356       this.isEsc              = ESgetpars;
3357 /*[*/ if (ch == 0x5B) { // Function key
3358         this.isEsc            = ESfunckey;
3359         break;
3360       } else {
3361 /*?*/   this.isQuestionMark   = ch == 0x3F;
3362         if (this.isQuestionMark) {
3363           break;
3364         }
3365       }
3366       // Fall through
3367     case ESdeviceattr:
3368     case ESgetpars: 
3369 /*;*/ if (ch == 0x3B) {
3370         this.npar++;
3371         break;
3372       } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3373         var par               = this.par[this.npar];
3374         if (par == undefined) {
3375           par                 = 0;
3376         }
3377         this.par[this.npar]   = 10*par + (ch & 0xF);
3378         break;
3379       } else if (this.isEsc == ESdeviceattr) {
3380         switch (ch) {
3381 /*c*/   case 0x63: if (this.par[0] == 0) this.respondSecondaryDA();     break;
3382 /*m*/   case 0x6D: /* (re)set key modifier resource values */           break;
3383 /*n*/   case 0x6E: /* disable key modifier resource values */           break;
3384 /*p*/   case 0x70: /* set pointer mode resource value */                break;
3385         default:                                                        break;
3386         }
3387         this.isEsc            = ESnormal;
3388         break;
3389       } else {
3390         this.isEsc            = ESgotpars;
3391       }
3392       // Fall through
3393     case ESgotpars:
3394       this.isEsc              = ESnormal;
3395       if (this.isQuestionMark) {
3396         switch (ch) {
3397 /*h*/   case 0x68: this.setMode(true);                                  break;
3398 /*l*/   case 0x6C: this.setMode(false);                                 break;
3399 /*c*/   case 0x63: this.setCursorAttr(this.par[2], this.par[1]);        break;
3400         default:                                                        break;
3401         }
3402         this.isQuestionMark   = false;
3403         break;
3404       }
3405       switch (ch) {
3406 /*!*/ case 0x21: this.isEsc   = ESbang;                                 break;
3407 /*>*/ case 0x3E: if (!this.npar) this.isEsc  = ESdeviceattr;            break;
3408 /*G*/ case 0x47:
3409 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY);            break;
3410 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3411                              this.cursorY - (this.par[0] ? this.par[0] : 1));
3412                                                                         break;
3413 /*B*/ case 0x42:
3414 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3415                              this.cursorY + (this.par[0] ? this.par[0] : 1));
3416                                                                         break;
3417 /*C*/ case 0x43:
3418 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3419                              this.cursorY);                             break;
3420 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3421                              this.cursorY);                             break;
3422 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3423                                                                         break;
3424 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3425                                                                         break;
3426 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1);           break;
3427 /*H*/ case 0x48:
3428 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1);        break;
3429 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1);                break;
3430 /*@*/ case 0x40: this.csiAt(this.par[0]);                               break;
3431 /*i*/ case 0x69: this.csii(this.par[0]);                                break;
3432 /*J*/ case 0x4A: this.csiJ(this.par[0]);                                break;
3433 /*K*/ case 0x4B: this.csiK(this.par[0]);                                break;
3434 /*L*/ case 0x4C: this.csiL(this.par[0]);                                break;
3435 /*M*/ case 0x4D: this.csiM(this.par[0]);                                break;
3436 /*m*/ case 0x6D: this.csim();                                           break;
3437 /*P*/ case 0x50: this.csiP(this.par[0]);                                break;
3438 /*X*/ case 0x58: this.csiX(this.par[0]);                                break;
3439 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1);                break;
3440 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1);                break;
3441 /*c*/ case 0x63: if (!this.par[0]) this.respondID();                    break;
3442 /*g*/ case 0x67: if (this.par[0] == 0) {
3443                    this.userTabStop[this.cursorX] = false;
3444                  } else if (this.par[0] == 2 || this.par[0] == 3) {
3445                    this.userTabStop               = [ ];
3446                    for (var i = 0; i < this.terminalWidth; i++) {
3447                      this.userTabStop[i]          = false;
3448                    }
3449                  }
3450                  break;
3451 /*h*/ case 0x68: this.setMode(true);                                    break;
3452 /*l*/ case 0x6C: this.setMode(false);                                   break;
3453 /*n*/ case 0x6E: switch (this.par[0]) {
3454                  case 5: this.statusReport();                           break;
3455                  case 6: this.cursorReport();                           break;
3456                  default:                                               break;
3457                  }
3458                  break;
3459 /*q*/ case 0x71: // LED control not implemented
3460                                                                         break;
3461 /*r*/ case 0x72: var t        = this.par[0] ? this.par[0] : 1;
3462                  var b        = this.par[1] ? this.par[1]
3463                                             : this.terminalHeight;
3464                  if (t < b && b <= this.terminalHeight) {
3465                    this.top   = t - 1;
3466                    this.bottom= b;
3467                    this.gotoXaY(0, 0);
3468                  }
3469                  break;
3470 /*b*/ case 0x62: var c        = this.par[0] ? this.par[0] : 1;
3471                  if (c > this.terminalWidth * this.terminalHeight) {
3472                    c          = this.terminalWidth * this.terminalHeight;
3473                  }
3474                  while (c-- > 0) {
3475                    lineBuf   += this.lastCharacter;
3476                  }
3477                  break;
3478 /*s*/ case 0x73: this.saveCursor();                                     break;
3479 /*u*/ case 0x75: this.restoreCursor();                                  break;
3480 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1);                break;
3481 /*]*/ case 0x5D: this.settermCommand();                                 break;
3482       default:                                                          break;
3483       }
3484       break;
3485     case ESbang:
3486       if (ch == 'p') {
3487         this.reset();
3488       }
3489       this.isEsc              = ESnormal;
3490       break;
3491     case ESpercent:
3492       this.isEsc              = ESnormal;
3493       switch (ch) {
3494 /*@*/ case 0x40: this.utfEnabled = false;                               break;
3495 /*G*/ case 0x47:
3496 /*8*/ case 0x38: this.utfEnabled = true;                                break;
3497       default:                                                          break;
3498       }
3499       break;
3500     case ESfunckey:
3501       this.isEsc              = ESnormal;                               break;
3502     case EShash:
3503       this.isEsc              = ESnormal;
3504 /*8*/ if (ch == 0x38) {
3505         // Screen alignment test not implemented
3506       }
3507       break;
3508     case ESsetG0:
3509     case ESsetG1:
3510     case ESsetG2:
3511     case ESsetG3:
3512       var g                   = this.isEsc - ESsetG0;
3513       this.isEsc              = ESnormal;
3514       switch (ch) {
3515 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap;                  break;
3516 /*A*/ case 0x42:
3517 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map;                         break;
3518 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map;                    break;
3519 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap;                   break;
3520       default:                                                          break;
3521       }
3522       if (this.useGMap == g) {
3523         this.translate        = this.GMap[g];
3524       }
3525       break;
3526     case ESstatus:
3527       if (ch == 0x07) {
3528         if (this.statusString && this.statusString.charAt(0) == ';') {
3529           this.statusString   = this.statusString.substr(1);
3530         }
3531         try {
3532           window.status       = this.statusString;
3533         } catch (e) {
3534         }
3535         this.isEsc            = ESnormal;
3536       } else {
3537         this.statusString    += String.fromCharCode(ch);
3538       }
3539       break;
3540     case ESss2:
3541     case ESss3:
3542       if (ch < 256) {
3543           ch                  = this.GMap[this.isEsc - ESss2 + 2]
3544                                          [this.toggleMeta ? (ch | 0x80) : ch];
3545         if ((ch & 0xFF00) == 0xF000) {
3546           ch                  = ch & 0xFF;
3547         } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3548           this.isEsc         = ESnormal;                                break;
3549         }
3550       }
3551       this.lastCharacter      = String.fromCharCode(ch);
3552       lineBuf                += this.lastCharacter;
3553       this.isEsc              = ESnormal;                               break;
3554     default:
3555       this.isEsc              = ESnormal;                               break;
3556     }
3557     break;
3558   }
3559   return lineBuf;
3560 };
3561
3562 VT100.prototype.renderString = function(s, showCursor) {
3563   if (this.printing) {
3564     this.sendToPrinter(s);
3565     if (showCursor) {
3566       this.showCursor();
3567     }
3568     return;
3569   }
3570
3571   // We try to minimize the number of DOM operations by coalescing individual
3572   // characters into strings. This is a significant performance improvement.
3573   var incX = s.length;
3574   if (incX > this.terminalWidth - this.cursorX) {
3575     incX   = this.terminalWidth - this.cursorX;
3576     if (incX <= 0) {
3577       return;
3578     }
3579     s      = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3580   }
3581   if (showCursor) {
3582     // Minimize the number of calls to putString(), by avoiding a direct
3583     // call to this.showCursor()
3584     this.cursor.style.visibility = '';
3585   }
3586   this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3587 };
3588
3589 VT100.prototype.vt100 = function(s) {
3590   this.cursorNeedsShowing = this.hideCursor();
3591   this.respondString      = '';
3592   var lineBuf             = '';
3593   for (var i = 0; i < s.length; i++) {
3594     var ch = s.charCodeAt(i);
3595     if (this.utfEnabled) {
3596       // Decode UTF8 encoded character
3597       if (ch > 0x7F) {
3598         if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3599           this.utfChar    = (this.utfChar << 6) | (ch & 0x3F);
3600           if (--this.utfCount <= 0) {
3601             if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3602               ch = 0xFFFD;
3603             } else {
3604               ch          = this.utfChar;
3605             }
3606           } else {
3607             continue;
3608           }
3609         } else {
3610           if ((ch & 0xE0) == 0xC0) {
3611             this.utfCount = 1;
3612             this.utfChar  = ch & 0x1F;
3613           } else if ((ch & 0xF0) == 0xE0) {
3614             this.utfCount = 2;
3615             this.utfChar  = ch & 0x0F;
3616           } else if ((ch & 0xF8) == 0xF0) {
3617             this.utfCount = 3;
3618             this.utfChar  = ch & 0x07;
3619           } else if ((ch & 0xFC) == 0xF8) {
3620             this.utfCount = 4;
3621             this.utfChar  = ch & 0x03;
3622           } else if ((ch & 0xFE) == 0xFC) {
3623             this.utfCount = 5;
3624             this.utfChar  = ch & 0x01;
3625           } else {
3626             this.utfCount = 0;
3627           }
3628           continue;
3629         }
3630       } else {
3631         this.utfCount     = 0;
3632       }
3633     }
3634     var isNormalCharacter =
3635       (ch >= 32 && ch <= 127 || ch >= 160 ||
3636        this.utfEnabled && ch >= 128 ||
3637        !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3638       (ch != 0x7F || this.dispCtrl);
3639     
3640     if (isNormalCharacter && this.isEsc == ESnormal) {
3641       if (ch < 256) {
3642         ch                = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3643       }
3644       if ((ch & 0xFF00) == 0xF000) {
3645         ch                = ch & 0xFF;
3646       } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3647         continue;
3648       }
3649       if (!this.printing) {
3650         if (this.needWrap || this.insertMode) {
3651           if (lineBuf) {
3652             this.renderString(lineBuf);
3653             lineBuf       = '';
3654           }
3655         }
3656         if (this.needWrap) {
3657           this.cr(); this.lf();
3658         }
3659         if (this.insertMode) {
3660           this.scrollRegion(this.cursorX, this.cursorY,
3661                             this.terminalWidth - this.cursorX - 1, 1,
3662                             1, 0, this.color, this.style);
3663         }
3664       }
3665       this.lastCharacter  = String.fromCharCode(ch);
3666       lineBuf            += this.lastCharacter;
3667       if (!this.printing &&
3668           this.cursorX + lineBuf.length >= this.terminalWidth) {
3669         this.needWrap     = this.autoWrapMode;
3670       }
3671     } else {
3672       if (lineBuf) {
3673         this.renderString(lineBuf);
3674         lineBuf           = '';
3675       }
3676       var expand          = this.doControl(ch);
3677       if (expand.length) {
3678         var r             = this.respondString;
3679         this.respondString= r + this.vt100(expand);
3680       }
3681     }
3682   }
3683   if (lineBuf) {
3684     this.renderString(lineBuf, this.cursorNeedsShowing);
3685   } else if (this.cursorNeedsShowing) {
3686     this.showCursor();
3687   }
3688   return this.respondString;
3689 };
3690
3691 VT100.prototype.Latin1Map = [
3692 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3693 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3694 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3695 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3696 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3697 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3698 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3699 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3700 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3701 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3702 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3703 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3704 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3705 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3706 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3707 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3708 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3709 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3710 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3711 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3712 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3713 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3714 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3715 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3716 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3717 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3718 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3719 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3720 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3721 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3722 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3723 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3724 ];
3725
3726 VT100.prototype.VT100GraphicsMap = [
3727 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3728 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3729 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3730 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3731 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3732 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3733 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3734 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3735 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3736 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3737 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3738 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3739 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3740 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3741 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3742 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3743 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3744 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3745 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3746 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3747 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3748 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3749 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3750 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3751 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3752 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3753 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3754 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3755 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3756 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3757 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3758 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3759 ];
3760
3761 VT100.prototype.CodePage437Map = [
3762 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3763 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3764 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3765 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3766 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3767 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3768 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3769 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3770 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3771 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3772 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3773 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3774 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3775 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3776 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3777 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3778 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3779 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3780 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3781 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3782 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3783 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3784 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3785 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3786 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3787 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3788 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3789 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3790 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3791 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3792 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3793 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3794 ];
3795
3796 VT100.prototype.DirectToFontMap = [
3797 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3798 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3799 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3800 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3801 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3802 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3803 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3804 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3805 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3806 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3807 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3808 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3809 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3810 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3811 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3812 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3813 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3814 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3815 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3816 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3817 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3818 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3819 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3820 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3821 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3822 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3823 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3824 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3825 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3826 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3827 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3828 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3829 ];
3830
3831 VT100.prototype.ctrlAction = [
3832   true,  false, false, false, false, false, false, true,
3833   true,  true,  true,  true,  true,  true,  true,  true,
3834   false, false, false, false, false, false, false, false,
3835   true,  false, true,  true,  false, false, false, false
3836 ];
3837
3838 VT100.prototype.ctrlAlways = [
3839   true,  false, false, false, false, false, false, false,
3840   true,  false, true,  false, true,  true,  true,  true,
3841   false, false, false, false, false, false, false, false,
3842   false, false, false, true,  false, false, false, false
3843 ];
3844
This page took 0.340563 seconds and 5 git commands to generate.