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