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