]> andersk Git - test.git/blob - shellinabox/vt100.jspp
8759856a2db3d0a5e5feb71bba52ef5e34670ea4
[test.git] / shellinabox / vt100.jspp
1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 //
17 // In addition to these license terms, the author grants the following
18 // additional rights:
19 //
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
28 //
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
31 //
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
35 //
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
38 //
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
41 //
42 //
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
45 //
46 //
47 // Notes:
48 //
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
56 //
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
62 //
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
65
66 #define ESnormal        0
67 #define ESesc           1
68 #define ESsquare        2
69 #define ESgetpars       3
70 #define ESgotpars       4
71 #define ESdeviceattr    5
72 #define ESfunckey       6
73 #define EShash          7
74 #define ESsetG0         8
75 #define ESsetG1         9
76 #define ESsetG2        10
77 #define ESsetG3        11
78 #define ESbang         12
79 #define ESpercent      13
80 #define ESignore       14
81 #define ESnonstd       15
82 #define ESpalette      16
83 #define ESstatus       17
84 #define ESss2          18
85 #define ESss3          19
86
87 #define ATTR_DEFAULT   0x00F0
88 #define ATTR_REVERSE   0x0100
89 #define ATTR_UNDERLINE 0x0200
90 #define ATTR_DIM       0x0400
91 #define ATTR_BRIGHT    0x0800
92 #define ATTR_BLINK     0x1000
93
94 #define MOUSE_DOWN     0
95 #define MOUSE_UP       1
96 #define MOUSE_CLICK    2
97
98 function VT100(container) {
99   if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
100     this.urlRE            = null;
101   } else {
102     this.urlRE            = new RegExp(
103     // Known URL protocol are "http", "https", and "ftp".
104     '(?:http|https|ftp)://' +
105
106     // Optionally allow username and passwords.
107     '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
108
109     // Hostname.
110     '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111     '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112     '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
113
114     // Port
115     '(?::[1-9][0-9]*)?' +
116
117     // Path.
118     '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
119
120     (linkifyURLs <= 1 ? '' :
121     // Also support URLs without a protocol (assume "http").
122     // Optional username and password.
123     '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
124
125     // Hostnames must end with a well-known top-level domain or must be
126     // numeric.
127     '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
128     'localhost|' +
129     '(?:(?!-)' +
130         '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131     '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132     'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133     'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134     'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135     'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136     'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137     'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138     'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139     'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140     'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141     'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142     'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143     'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
144
145     // Port
146     '(?::[1-9][0-9]{0,4})?' +
147
148     // Path.
149     '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
150
151     // In addition, support e-mail address. Optionally, recognize "mailto:"
152     '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
153
154     // Username:
155     '[-_.+a-zA-Z0-9]+@' +
156
157     // Hostname.
158     '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159     '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160     'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161     'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162     'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163     'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164     'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165     'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166     'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167     'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168     'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169     'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170     'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171     'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
172
173     // Optional arguments
174     '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
175   }
176   this.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                            = 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                             = 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, MOUSE_DOWN));
585   this.addListener(this.scrollable,'mouseup',  mouseEvent(this, MOUSE_UP));
586   this.addListener(this.scrollable,'click',    mouseEvent(this, 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 == MOUSE_UP || type == 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 != 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 != MOUSE_DOWN || !event.shiftKey)) {
895     if (inside || type != 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 != 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 == 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 " + VERSION +
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     event.keyCode == 226;
2256   var normalKey                 =
2257     alphNumKey                                   ||
2258     event.keyCode ==  59 || event.keyCode ==  61 ||
2259     event.keyCode == 106 || event.keyCode == 107 ||
2260     event.keyCode >= 109 && event.keyCode <= 111 ||
2261     event.keyCode >= 186 && event.keyCode <= 192 ||
2262     event.keyCode >= 219 && event.keyCode <= 222 ||
2263     event.keyCode == 252;
2264   try {
2265     if (navigator.appName == 'Konqueror') {
2266       normalKey                |= event.keyCode < 128;
2267     }
2268   } catch (e) {
2269   }
2270
2271   // We normally prefer to look at keypress events, as they perform the
2272   // translation from keyCode to charCode. This is important, as the
2273   // translation is locale-dependent.
2274   // But for some keys, we must intercept them during the keydown event,
2275   // as they would otherwise get interpreted by the browser.
2276   // Even, when doing all of this, there are some keys that we can never
2277   // intercept. This applies to some of the menu navigation keys in IE.
2278   // In fact, we see them, but we cannot stop IE from seeing them, too.
2279   if ((event.charCode || event.keyCode) &&
2280       ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2281         !event.shiftKey &&
2282         // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2283         // interpret this sequence ourselves, as some keyboard layouts use
2284         // it for second-level layouts.
2285         !(event.ctrlKey && event.altKey)) ||
2286        this.catchModifiersEarly && normalKey && !alphNumKey &&
2287        (event.ctrlKey || event.altKey || event.metaKey) ||
2288        !normalKey)) {
2289     this.lastKeyDownEvent       = event;
2290     var fake                    = [ ];
2291     fake.ctrlKey                = event.ctrlKey;
2292     fake.shiftKey               = event.shiftKey;
2293     fake.altKey                 = event.altKey;
2294     fake.metaKey                = event.metaKey;
2295     if (asciiKey) {
2296       fake.charCode             = event.keyCode;
2297       fake.keyCode              = 0;
2298     } else {
2299       fake.charCode             = 0;
2300       fake.keyCode              = event.keyCode;
2301       if (!alphNumKey && event.shiftKey) {
2302         fake                    = this.fixEvent(fake);
2303       }
2304     }
2305
2306     this.handleKey(fake);
2307     this.lastNormalKeyDownEvent = undefined;
2308
2309     try {
2310       // For non-IE browsers
2311       event.stopPropagation();
2312       event.preventDefault();
2313     } catch (e) {
2314     }
2315     try {
2316       // For IE
2317       event.cancelBubble = true;
2318       event.returnValue  = false;
2319       event.keyCode      = 0;
2320     } catch (e) {
2321     }
2322
2323     return false;
2324   }
2325   return true;
2326 };
2327
2328 VT100.prototype.keyPressed = function(event) {
2329   // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2330   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2331   //             event.metaKey ? ', ' +
2332   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2333   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2334   //            '\r\n');
2335   if (this.lastKeyDownEvent) {
2336     // If we already processed the key on keydown, do not process it
2337     // again here. Ideally, the browser should not even have generated a
2338     // keypress event in this case. But that does not appear to always work.
2339     this.lastKeyDownEvent     = undefined;
2340   } else {
2341     this.handleKey(event.altKey || event.metaKey
2342                    ? this.fixEvent(event) : event);
2343   }
2344
2345   try {
2346     // For non-IE browsers
2347     event.preventDefault();
2348   } catch (e) {
2349   }
2350
2351   try {
2352     // For IE
2353     event.cancelBubble = true;
2354     event.returnValue  = false;
2355     event.keyCode      = 0;
2356   } catch (e) {
2357   }
2358
2359   this.lastNormalKeyDownEvent = undefined;
2360   this.lastKeyPressedEvent    = event;
2361   return false;
2362 };
2363
2364 VT100.prototype.keyUp = function(event) {
2365   // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2366   //            (event.shiftKey || event.ctrlKey || event.altKey ||
2367   //             event.metaKey ? ', ' +
2368   //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2369   //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2370   //            '\r\n');
2371   if (this.lastKeyPressedEvent) {
2372     // The compose key on Linux occasionally confuses the browser and keeps
2373     // inserting bogus characters into the input field, even if just a regular
2374     // key has been pressed. Detect this case and drop the bogus characters.
2375     (event.target ||
2376      event.srcElement).value      = '';
2377   } else {
2378     // This is usually were we notice that a key has been composed and
2379     // thus failed to generate normal events.
2380     this.checkComposedKeys(event);
2381
2382     // Some browsers don't report keypress events if ctrl or alt is pressed
2383     // for non-alphanumerical keys. Patch things up for now, but in the
2384     // future we will catch these keys earlier (in the keydown handler).
2385     if (this.lastNormalKeyDownEvent) {
2386       this.catchModifiersEarly    = true;
2387       var asciiKey                =
2388         event.keyCode ==  32                         ||
2389         event.keyCode >=  48 && event.keyCode <=  57 ||
2390         event.keyCode >=  65 && event.keyCode <=  90;
2391       var alphNumKey              =
2392         asciiKey                                     ||
2393         event.keyCode >=  96 && event.keyCode <= 105;
2394       var normalKey               =
2395         alphNumKey                                   ||
2396         event.keyCode ==  59 || event.keyCode ==  61 ||
2397         event.keyCode == 106 || event.keyCode == 107 ||
2398         event.keyCode >= 109 && event.keyCode <= 111 ||
2399         event.keyCode >= 186 && event.keyCode <= 192 ||
2400         event.keyCode >= 219 && event.keyCode <= 222 ||
2401         event.keyCode == 252;
2402       var fake                    = [ ];
2403       fake.ctrlKey                = event.ctrlKey;
2404       fake.shiftKey               = event.shiftKey;
2405       fake.altKey                 = event.altKey;
2406       fake.metaKey                = event.metaKey;
2407       if (asciiKey) {
2408         fake.charCode             = event.keyCode;
2409         fake.keyCode              = 0;
2410       } else {
2411         fake.charCode             = 0;
2412         fake.keyCode              = event.keyCode;
2413         if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2414           fake                    = this.fixEvent(fake);
2415         }
2416       }
2417       this.lastNormalKeyDownEvent = undefined;
2418       this.handleKey(fake);
2419     }
2420   }
2421
2422   try {
2423     // For IE
2424     event.cancelBubble            = true;
2425     event.returnValue             = false;
2426     event.keyCode                 = 0;
2427   } catch (e) {
2428   }
2429
2430   this.lastKeyDownEvent           = undefined;
2431   this.lastKeyPressedEvent        = undefined;
2432   return false;
2433 };
2434
2435 VT100.prototype.animateCursor = function(inactive) {
2436   if (!this.cursorInterval) {
2437     this.cursorInterval     = setInterval(
2438       function(vt100) {
2439         return function() {
2440           vt100.animateCursor();
2441
2442           // Use this opportunity to check whether the user entered a composed
2443           // key, or whether somebody pasted text into the textfield.
2444           vt100.checkComposedKeys();
2445         }
2446       }(this), 500);
2447   }
2448   if (inactive != undefined || this.cursor.className != 'inactive') {
2449     if (inactive) {
2450       this.cursor.className = 'inactive';
2451     } else {
2452       this.cursor.className = this.cursor.className == 'bright'
2453                               ? 'dim' : 'bright';
2454     }
2455   }
2456 };
2457
2458 VT100.prototype.blurCursor = function() {
2459   this.animateCursor(true);
2460 };
2461
2462 VT100.prototype.focusCursor = function() {
2463   this.animateCursor(false);
2464 };
2465
2466 VT100.prototype.flashScreen = function() {
2467   this.isInverted       = !this.isInverted;
2468   this.refreshInvertedState();
2469   this.isInverted       = !this.isInverted;
2470   setTimeout(function(vt100) {
2471                return function() {
2472                  vt100.refreshInvertedState();
2473                };
2474              }(this), 100);
2475 };
2476
2477 VT100.prototype.beep = function() {
2478   if (this.visualBell) {
2479     this.flashScreen();
2480   } else {
2481     try {
2482       this.beeper.Play();
2483     } catch (e) {
2484       try {
2485         this.beeper.src = 'beep.wav';
2486       } catch (e) {
2487       }
2488     }
2489   }
2490 };
2491
2492 VT100.prototype.bs = function() {
2493   if (this.cursorX > 0) {
2494     this.gotoXY(this.cursorX - 1, this.cursorY);
2495     this.needWrap = false;
2496   }
2497 };
2498
2499 VT100.prototype.ht = function(count) {
2500   if (count == undefined) {
2501     count        = 1;
2502   }
2503   var cx         = this.cursorX;
2504   while (count-- > 0) {
2505     while (cx++ < this.terminalWidth) {
2506       var tabState = this.userTabStop[cx];
2507       if (tabState == false) {
2508         // Explicitly cleared tab stop
2509         continue;
2510       } else if (tabState) {
2511         // Explicitly set tab stop
2512         break;
2513       } else {
2514         // Default tab stop at each eighth column
2515         if (cx % 8 == 0) {
2516           break;
2517         }
2518       }
2519     }
2520   }
2521   if (cx > this.terminalWidth - 1) {
2522     cx           = this.terminalWidth - 1;
2523   }
2524   if (cx != this.cursorX) {
2525     this.gotoXY(cx, this.cursorY);
2526   }
2527 };
2528
2529 VT100.prototype.rt = function(count) {
2530   if (count == undefined) {
2531     count          = 1 ;
2532   }
2533   var cx           = this.cursorX;
2534   while (count-- > 0) {
2535     while (cx-- > 0) {
2536       var tabState = this.userTabStop[cx];
2537       if (tabState == false) {
2538         // Explicitly cleared tab stop
2539         continue;
2540       } else if (tabState) {
2541         // Explicitly set tab stop
2542         break;
2543       } else {
2544         // Default tab stop at each eighth column
2545         if (cx % 8 == 0) {
2546           break;
2547         }
2548       }
2549     }
2550   }
2551   if (cx < 0) {
2552     cx             = 0;
2553   }
2554   if (cx != this.cursorX) {
2555     this.gotoXY(cx, this.cursorY);
2556   }
2557 };
2558
2559 VT100.prototype.cr = function() {
2560   this.gotoXY(0, this.cursorY);
2561   this.needWrap = false;
2562 };
2563
2564 VT100.prototype.lf = function(count) {
2565   if (count == undefined) {
2566     count    = 1;
2567   } else {
2568     if (count > this.terminalHeight) {
2569       count  = this.terminalHeight;
2570     }
2571     if (count < 1) {
2572       count  = 1;
2573     }
2574   }
2575   while (count-- > 0) {
2576     if (this.cursorY == this.bottom - 1) {
2577       this.scrollRegion(0, this.top + 1,
2578                         this.terminalWidth, this.bottom - this.top - 1,
2579                         0, -1, this.style);
2580       offset = undefined;
2581     } else if (this.cursorY < this.terminalHeight - 1) {
2582       this.gotoXY(this.cursorX, this.cursorY + 1);
2583     }
2584   }
2585 };
2586
2587 VT100.prototype.ri = function(count) {
2588   if (count == undefined) {
2589     count   = 1;
2590   } else {
2591     if (count > this.terminalHeight) {
2592       count = this.terminalHeight;
2593     }
2594     if (count < 1) {
2595       count = 1;
2596     }
2597   }
2598   while (count-- > 0) {
2599     if (this.cursorY == this.top) {
2600       this.scrollRegion(0, this.top,
2601                         this.terminalWidth, this.bottom - this.top - 1,
2602                         0, 1, this.style);
2603     } else if (this.cursorY > 0) {
2604       this.gotoXY(this.cursorX, this.cursorY - 1);
2605     }
2606   }
2607   this.needWrap = false;
2608 };
2609
2610 VT100.prototype.respondID = function() {
2611   this.respondString += '\u001B[?6c';
2612 };
2613
2614 VT100.prototype.respondSecondaryDA = function() {
2615   this.respondString += '\u001B[>0;0;0c';
2616 };
2617
2618 VT100.prototype.updateStyle = function() {
2619   var style  = '';
2620   if (this.attr & ATTR_UNDERLINE) {
2621     style   += 'text-decoration:underline;';
2622   }
2623   var bg     = (this.attr >> 4) & 0xF;
2624   var fg     =  this.attr       & 0xF;
2625   if (this.attr & ATTR_REVERSE) {
2626     var tmp  = bg;
2627     bg       = fg;
2628     fg       = tmp;
2629   }
2630   if ((this.attr & (ATTR_REVERSE | ATTR_DIM)) == ATTR_DIM) {
2631     fg       = 8; // Dark grey
2632   } else if (this.attr & ATTR_BRIGHT) {
2633     fg      |= 8;
2634   }
2635   if (this.attr & ATTR_BLINK) {
2636     bg      ^= 8;
2637   }
2638   // Make some readability enhancements. Most notably, disallow identical
2639   // background and foreground colors.
2640   if (bg == fg) {
2641     if ((fg ^= 8) == 7) {
2642       fg     = 8;
2643     }
2644   }
2645   // And disallow bright colors on a light-grey background.
2646   if (bg == 7 && fg >= 8) {
2647     if ((fg -= 8) == 7) {
2648       fg     = 8;
2649     }
2650   }
2651
2652   if (fg != 0) {
2653     style += 'color:' + this.ansi[fg] + ';';
2654   }
2655   if (bg != 15) {
2656     style += 'background-color:' + this.ansi[bg] + ';';
2657   }
2658   this.attributeHelper.cssText = style;
2659   this.style                   = this.attributeHelper.cssText;
2660 };
2661
2662 VT100.prototype.setAttrColors = function(attr) {
2663   if (attr != this.attr) {
2664     this.attr = attr;
2665     this.updateStyle();
2666   }
2667 };
2668
2669 VT100.prototype.saveCursor = function() {
2670   this.savedX[this.currentScreen]     = this.cursorX;
2671   this.savedY[this.currentScreen]     = this.cursorY;
2672   this.savedAttr[this.currentScreen]  = this.attr;
2673   this.savedUseGMap                   = this.useGMap;
2674   for (var i = 0; i < 4; i++) {
2675     this.savedGMap[i]                 = this.GMap[i];
2676   }
2677   this.savedValid[this.currentScreen] = true;
2678 };
2679
2680 VT100.prototype.restoreCursor = function() {
2681   if (!this.savedValid[this.currentScreen]) {
2682     return;
2683   }
2684   this.attr      = this.savedAttr[this.currentScreen];
2685   this.updateStyle();
2686   this.useGMap   = this.savedUseGMap;
2687   for (var i = 0; i < 4; i++) {
2688     this.GMap[i] = this.savedGMap[i];
2689   }
2690   this.translate = this.GMap[this.useGMap];
2691   this.needWrap  = false;
2692   this.gotoXY(this.savedX[this.currentScreen],
2693               this.savedY[this.currentScreen]);
2694 };
2695
2696 VT100.prototype.setMode = function(state) {
2697   for (var i = 0; i <= this.npar; i++) {
2698     if (this.isQuestionMark) {
2699       switch (this.par[i]) {
2700       case  1: this.cursorKeyMode      = state;                      break;
2701       case  3: /* Toggling between 80/132 mode is not implemented */ break;
2702       case  5: this.isInverted = state; this.refreshInvertedState(); break;
2703       case  6: this.offsetMode         = state;                      break;
2704       case  7: this.autoWrapMode       = state;                      break;
2705       case 1000:
2706       case  9: this.mouseReporting     = state;                      break;
2707       case 25: this.cursorNeedsShowing = state;
2708                if (state) { this.showCursor(); }
2709                else       { this.hideCursor(); }                     break;
2710       case 1047:
2711       case 1049:
2712       case 47: this.enableAlternateScreen(state);                    break;
2713       default:                                                       break;
2714       }
2715     } else {
2716       switch (this.par[i]) {
2717       case  3: this.dispCtrl           = state;                      break;
2718       case  4: this.insertMode         = state;                      break;
2719       case  20:this.crLfMode           = state;                      break;
2720       default:                                                       break;
2721       }
2722     }
2723   }
2724 };
2725
2726 VT100.prototype.statusReport = function() {
2727   // Ready and operational.
2728   this.respondString += '\u001B[0n';
2729 };
2730
2731 VT100.prototype.cursorReport = function() {
2732   this.respondString += '\u001B[' +
2733                         (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2734                         ';' +
2735                         (this.cursorX + 1) +
2736                         'R';
2737 };
2738
2739 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2740   // Changing of cursor color is not implemented.
2741 };
2742
2743 VT100.prototype.csiAt = function(number) {
2744   // Insert spaces
2745   if (number == 0) {
2746     number      = 1;
2747   }
2748   if (number > this.terminalWidth - this.cursorX) {
2749     number      = this.terminalWidth - this.cursorX;
2750   }
2751   this.scrollRegion(this.cursorX, this.cursorY,
2752                     this.terminalWidth - this.cursorX - number, 1,
2753                     number, 0, this.style);
2754   this.needWrap = false;
2755 };
2756
2757 VT100.prototype.csiJ = function(number) {
2758   switch (number) {
2759   case 0: // Erase from cursor to end of display
2760     this.clearRegion(this.cursorX, this.cursorY,
2761                      this.terminalWidth - this.cursorX, 1, this.style);
2762     if (this.cursorY < this.terminalHeight-2) {
2763       this.clearRegion(0, this.cursorY+1,
2764                        this.terminalWidth, this.terminalHeight-this.cursorY-1,
2765                        this.style);
2766     }
2767     break;
2768   case 1: // Erase from start to cursor
2769     if (this.cursorY > 0) {
2770       this.clearRegion(0, 0,
2771                        this.terminalWidth, this.cursorY, this.style);
2772     }
2773     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2774     break;
2775   case 2: // Erase whole display
2776     this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
2777     break;
2778   default:
2779     return;
2780   }
2781   needWrap = false;
2782 };
2783
2784 VT100.prototype.csiK = function(number) {
2785   switch (number) {
2786   case 0: // Erase from cursor to end of line
2787     this.clearRegion(this.cursorX, this.cursorY,
2788                      this.terminalWidth - this.cursorX, 1, this.style);
2789     break;
2790   case 1: // Erase from start of line to cursor
2791     this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2792     break;
2793   case 2: // Erase whole line
2794     this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
2795     break;
2796   default:
2797     return;
2798   }
2799   needWrap = false;
2800 };
2801
2802 VT100.prototype.csiL = function(number) {
2803   // Open line by inserting blank line(s)
2804   if (this.cursorY >= this.bottom) {
2805     return;
2806   }
2807   if (number == 0) {
2808     number = 1;
2809   }
2810   if (number > this.bottom - this.cursorY) {
2811     number = this.bottom - this.cursorY;
2812   }
2813   this.scrollRegion(0, this.cursorY,
2814                     this.terminalWidth, this.bottom - this.cursorY - number,
2815                     0, number, this.style);
2816   needWrap = false;
2817 };
2818
2819 VT100.prototype.csiM = function(number) {
2820   // Delete line(s), scrolling up the bottom of the screen.
2821   if (this.cursorY >= this.bottom) {
2822     return;
2823   }
2824   if (number == 0) {
2825     number = 1;
2826   }
2827   if (number > this.bottom - this.cursorY) {
2828     number = bottom - cursorY;
2829   }
2830   this.scrollRegion(0, this.cursorY + number,
2831                     this.terminalWidth, this.bottom - this.cursorY - number,
2832                     0, -number, this.style);
2833   needWrap = false;
2834 };
2835
2836 VT100.prototype.csim = function() {
2837   for (var i = 0; i <= this.npar; i++) {
2838     switch (this.par[i]) {
2839     case 0:  this.attr  = ATTR_DEFAULT;                                break;
2840     case 1:  this.attr  = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT;         break;
2841     case 2:  this.attr  = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM;         break;
2842     case 4:  this.attr |= ATTR_UNDERLINE;                              break;
2843     case 5:  this.attr |= ATTR_BLINK;                                  break;
2844     case 7:  this.attr |= ATTR_REVERSE;                                break;
2845     case 10:
2846       this.translate    = this.GMap[this.useGMap];
2847       this.dispCtrl     = false;
2848       this.toggleMeta   = false;
2849       break;
2850     case 11:
2851       this.translate    = this.CodePage437Map;
2852       this.dispCtrl     = true;
2853       this.toggleMeta   = false;
2854       break;
2855     case 12:
2856       this.translate    = this.CodePage437Map;
2857       this.dispCtrl     = true;
2858       this.toggleMeta   = true;
2859       break;
2860     case 21:
2861     case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM);                     break;
2862     case 24: this.attr &= ~ ATTR_UNDERLINE;                            break;
2863     case 25: this.attr &= ~ ATTR_BLINK;                                break;
2864     case 27: this.attr &= ~ ATTR_REVERSE;                              break;
2865     case 38: this.attr  = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|
2866                           ATTR_UNDERLINE;                              break;
2867     case 39: this.attr &= ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F); break;
2868     case 49: this.attr |= 0xF0;                                        break;
2869     default:
2870       if (this.par[i] >= 30 && this.par[i] <= 37) {
2871           var fg        = this.par[i] - 30;
2872           this.attr     = (this.attr & ~0x0F) | fg;
2873       } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2874           var bg        = this.par[i] - 40;
2875           this.attr     = (this.attr & ~0xF0) | (bg << 4);
2876       }
2877       break;
2878     }
2879   }
2880   this.updateStyle();
2881 };
2882
2883 VT100.prototype.csiP = function(number) {
2884   // Delete character(s) following cursor
2885   if (number == 0) {
2886     number = 1;
2887   }
2888   if (number > this.terminalWidth - this.cursorX) {
2889     number = this.terminalWidth - this.cursorX;
2890   }
2891   this.scrollRegion(this.cursorX + number, this.cursorY,
2892                     this.terminalWidth - this.cursorX - number, 1,
2893                     -number, 0, this.style);
2894   needWrap = false;
2895 };
2896
2897 VT100.prototype.csiX = function(number) {
2898   // Clear characters following cursor
2899   if (number == 0) {
2900     number++;
2901   }
2902   if (number > this.terminalWidth - this.cursorX) {
2903     number = this.terminalWidth - this.cursorX;
2904   }
2905   this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
2906   needWrap = false;
2907 };
2908
2909 VT100.prototype.settermCommand = function() {
2910   // Setterm commands are not implemented
2911 };
2912
2913 VT100.prototype.doControl = function(ch) {
2914   var lineBuf                = '';
2915   switch (ch) {
2916   case 0x00: /* ignored */                                              break;
2917   case 0x08: this.bs();                                                 break;
2918   case 0x09: this.ht();                                                 break;
2919   case 0x0A:
2920   case 0x0B:
2921   case 0x0C:
2922   case 0x84: this.lf(); if (!this.crLfMode)                             break;
2923   case 0x0D: this.cr();                                                 break;
2924   case 0x85: this.cr(); this.lf();                                      break;
2925   case 0x0E: this.useGMap     = 1;
2926              this.translate   = this.GMap[1];
2927              this.dispCtrl    = true;                                   break;
2928   case 0x0F: this.useGMap     = 0;
2929              this.translate   = this.GMap[0];
2930              this.dispCtrl    = false;                                  break;
2931   case 0x18:
2932   case 0x1A: this.isEsc       = ESnormal;                               break;
2933   case 0x1B: this.isEsc       = ESesc;                                  break;
2934   case 0x7F: /* ignored */                                              break;
2935   case 0x88: this.userTabStop[this.cursorX] = true;                     break;
2936   case 0x8D: this.ri();                                                 break;
2937   case 0x8E: this.isEsc       = ESss2;                                  break;
2938   case 0x8F: this.isEsc       = ESss3;                                  break;
2939   case 0x9A: this.respondID();                                          break;
2940   case 0x9B: this.isEsc       = ESsquare;                               break;
2941   case 0x07: if (this.isEsc != ESstatus) {
2942                this.beep();                                             break;
2943              }
2944              /* fall thru */
2945   default:   switch (this.isEsc) {
2946     case ESesc:
2947       this.isEsc              = ESnormal;
2948       switch (ch) {
2949 /*%*/ case 0x25: this.isEsc   = ESpercent;                              break;
2950 /*(*/ case 0x28: this.isEsc   = ESsetG0;                                break;
2951 /*-*/ case 0x2D:
2952 /*)*/ case 0x29: this.isEsc   = ESsetG1;                                break;
2953 /*.*/ case 0x2E:
2954 /***/ case 0x2A: this.isEsc   = ESsetG2;                                break;
2955 /*/*/ case 0x2F:
2956 /*+*/ case 0x2B: this.isEsc   = ESsetG3;                                break;
2957 /*#*/ case 0x23: this.isEsc   = EShash;                                 break;
2958 /*7*/ case 0x37: this.saveCursor();                                     break;
2959 /*8*/ case 0x38: this.restoreCursor();                                  break;
2960 /*>*/ case 0x3E: this.applKeyMode = false;                              break;
2961 /*=*/ case 0x3D: this.applKeyMode = true;                               break;
2962 /*D*/ case 0x44: this.lf();                                             break;
2963 /*E*/ case 0x45: this.cr(); this.lf();                                  break;
2964 /*M*/ case 0x4D: this.ri();                                             break;
2965 /*N*/ case 0x4E: this.isEsc   = ESss2;                                  break;
2966 /*O*/ case 0x4F: this.isEsc   = ESss3;                                  break;
2967 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true;                 break;
2968 /*Z*/ case 0x5A: this.respondID();                                      break;
2969 /*[*/ case 0x5B: this.isEsc   = ESsquare;                               break;
2970 /*]*/ case 0x5D: this.isEsc   = ESnonstd;                               break;
2971 /*c*/ case 0x63: this.reset();                                          break;
2972 /*g*/ case 0x67: this.flashScreen();                                    break;
2973       default:                                                          break;
2974       }
2975       break;
2976     case ESnonstd:
2977       switch (ch) {
2978 /*0*/ case 0x30:
2979 /*1*/ case 0x31:
2980 /*2*/ case 0x32: this.statusString = ''; this.isEsc  = ESstatus;        break;
2981 /*P*/ case 0x50: this.npar    = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
2982                  this.isEsc   = ESpalette;                              break;
2983 /*R*/ case 0x52: // Palette support is not implemented
2984                  this.isEsc   = ESnormal;                               break;
2985       default:   this.isEsc   = ESnormal;                               break;
2986       }
2987       break;
2988     case ESpalette:
2989       if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
2990           (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
2991           (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
2992         this.par[this.npar++] = ch > 0x39  /*9*/ ? (ch & 0xDF) - 55
2993                                                 : (ch & 0xF);
2994         if (this.npar == 7) {
2995           // Palette support is not implemented
2996           this.isEsc          = ESnormal;
2997         }
2998       } else {
2999         this.isEsc            = ESnormal;
3000       }
3001       break;
3002     case ESsquare:
3003       this.npar               = 0;
3004       this.par                = [ 0, 0, 0, 0, 0, 0, 0, 0,
3005                                   0, 0, 0, 0, 0, 0, 0, 0 ];
3006       this.isEsc              = ESgetpars;
3007 /*[*/ if (ch == 0x5B) { // Function key
3008         this.isEsc            = ESfunckey;
3009         break;
3010       } else {
3011 /*?*/   this.isQuestionMark   = ch == 0x3F;
3012         if (this.isQuestionMark) {
3013           break;
3014         }
3015       }
3016       // Fall through
3017     case ESdeviceattr:
3018     case ESgetpars: 
3019 /*;*/ if (ch == 0x3B) {
3020         this.npar++;
3021         break;
3022       } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3023         var par               = this.par[this.npar];
3024         if (par == undefined) {
3025           par                 = 0;
3026         }
3027         this.par[this.npar]   = 10*par + (ch & 0xF);
3028         break;
3029       } else if (this.isEsc == ESdeviceattr) {
3030         switch (ch) {
3031 /*c*/   case 0x63: if (this.par[0] == 0) this.respondSecondaryDA();     break;
3032 /*m*/   case 0x6D: /* (re)set key modifier resource values */           break;
3033 /*n*/   case 0x6E: /* disable key modifier resource values */           break;
3034 /*p*/   case 0x70: /* set pointer mode resource value */                break;
3035         default:                                                        break;
3036         }
3037         this.isEsc            = ESnormal;
3038         break;
3039       } else {
3040         this.isEsc            = ESgotpars;
3041       }
3042       // Fall through
3043     case ESgotpars:
3044       this.isEsc              = ESnormal;
3045       if (this.isQuestionMark) {
3046         switch (ch) {
3047 /*h*/   case 0x68: this.setMode(true);                                  break;
3048 /*l*/   case 0x6C: this.setMode(false);                                 break;
3049 /*c*/   case 0x63: this.setCursorAttr(this.par[2], this.par[1]);        break;
3050         default:                                                        break;
3051         }
3052         this.isQuestionMark   = false;
3053         break;
3054       }
3055       switch (ch) {
3056 /*!*/ case 0x21: this.isEsc   = ESbang;                                 break;
3057 /*>*/ case 0x3E: if (!this.npar) this.isEsc  = ESdeviceattr;            break;
3058 /*G*/ case 0x47:
3059 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY);            break;
3060 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3061                              this.cursorY - (this.par[0] ? this.par[0] : 1));
3062                                                                         break;
3063 /*B*/ case 0x42:
3064 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3065                              this.cursorY + (this.par[0] ? this.par[0] : 1));
3066                                                                         break;
3067 /*C*/ case 0x43:
3068 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3069                              this.cursorY);                             break;
3070 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3071                              this.cursorY);                             break;
3072 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3073                                                                         break;
3074 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3075                                                                         break;
3076 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1);           break;
3077 /*H*/ case 0x48:
3078 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1);        break;
3079 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1);                break;
3080 /*@*/ case 0x40: this.csiAt(this.par[0]);                               break;
3081 /*J*/ case 0x4A: this.csiJ(this.par[0]);                                break;
3082 /*K*/ case 0x4B: this.csiK(this.par[0]);                                break;
3083 /*L*/ case 0x4C: this.csiL(this.par[0]);                                break;
3084 /*M*/ case 0x4D: this.csiM(this.par[0]);                                break;
3085 /*m*/ case 0x6D: this.csim();                                           break;
3086 /*P*/ case 0x50: this.csiP(this.par[0]);                                break;
3087 /*X*/ case 0x58: this.csiX(this.par[0]);                                break;
3088 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1);                break;
3089 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1);                break;
3090 /*c*/ case 0x63: if (!this.par[0]) this.respondID();                    break;
3091 /*g*/ case 0x67: if (this.par[0] == 0) {
3092                    this.userTabStop[this.cursorX] = false;
3093                  } else if (this.par[0] == 2 || this.par[0] == 3) {
3094                    this.userTabStop               = [ ];
3095                    for (var i = 0; i < this.terminalWidth; i++) {
3096                      this.userTabStop[i]          = false;
3097                    }
3098                  }
3099                  break;
3100 /*h*/ case 0x68: this.setMode(true);                                    break;
3101 /*l*/ case 0x6C: this.setMode(false);                                   break;
3102 /*n*/ case 0x6E: switch (this.par[0]) {
3103                  case 5: this.statusReport();                           break;
3104                  case 6: this.cursorReport();                           break;
3105                  default:                                               break;
3106                  }
3107                  break;
3108 /*q*/ case 0x71: // LED control not implemented
3109                                                                         break;
3110 /*r*/ case 0x72: var t        = this.par[0] ? this.par[0] : 1;
3111                  var b        = this.par[1] ? this.par[1]
3112                                             : this.terminalHeight;
3113                  if (t < b && b <= this.terminalHeight) {
3114                    this.top   = t - 1;
3115                    this.bottom= b;
3116                    this.gotoXaY(0, 0);
3117                  }
3118                  break;
3119 /*b*/ case 0x62: var c        = this.par[0] ? this.par[0] : 1;
3120                  if (c > this.terminalWidth * this.terminalHeight) {
3121                    c          = this.terminalWidth * this.terminalHeight;
3122                  }
3123                  while (c-- > 0) {
3124                    lineBuf   += this.lastCharacter;
3125                  }
3126                  break;
3127 /*s*/ case 0x73: this.saveCursor();                                     break;
3128 /*u*/ case 0x75: this.restoreCursor();                                  break;
3129 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1);                break;
3130 /*]*/ case 0x5D: this.settermCommand();                                 break;
3131       default:                                                          break;
3132       }
3133       break;
3134     case ESbang:
3135       if (ch == 'p') {
3136         this.reset();
3137       }
3138       this.isEsc              = ESnormal;
3139       break;
3140     case ESpercent:
3141       this.isEsc              = ESnormal;
3142       switch (ch) {
3143 /*@*/ case 0x40: this.utfEnabled = false;                               break;
3144 /*G*/ case 0x47:
3145 /*8*/ case 0x38: this.utfEnabled = true;                                break;
3146       default:                                                          break;
3147       }
3148       break;
3149     case ESfunckey:
3150       this.isEsc              = ESnormal;                               break;
3151     case EShash:
3152       this.isEsc              = ESnormal;
3153 /*8*/ if (ch == 0x38) {
3154         // Screen alignment test not implemented
3155       }
3156       break;
3157     case ESsetG0:
3158     case ESsetG1:
3159     case ESsetG2:
3160     case ESsetG3:
3161       var g                   = this.isEsc - ESsetG0;
3162       this.isEsc              = ESnormal;
3163       switch (ch) {
3164 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap;                  break;
3165 /*A*/ case 0x42:
3166 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map;                         break;
3167 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map;                    break;
3168 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap;                   break;
3169       default:                                                          break;
3170       }
3171       if (this.useGMap == g) {
3172         this.translate        = this.GMap[g];
3173       }
3174       break;
3175     case ESstatus:
3176       if (ch == 0x07) {
3177         if (this.statusString && this.statusString.charAt(0) == ';') {
3178           this.statusString   = this.statusString.substr(1);
3179         }
3180         try {
3181           window.status       = this.statusString;
3182         } catch (e) {
3183         }
3184         this.isEsc            = ESnormal;
3185       } else {
3186         this.statusString    += String.fromCharCode(ch);
3187       }
3188       break;
3189     case ESss2:
3190     case ESss3:
3191       if (ch < 256) {
3192           ch                  = this.GMap[this.isEsc - ESss2 + 2]
3193                                          [this.toggleMeta ? (ch | 0x80) : ch];
3194         if ((ch & 0xFF00) == 0xF000) {
3195           ch                  = ch & 0xFF;
3196         } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3197           this.isEsc         = ESnormal;                                break;
3198         }
3199       }
3200       this.lastCharacter      = String.fromCharCode(ch);
3201       lineBuf                += this.lastCharacter;
3202       this.isEsc              = ESnormal;                               break;
3203     default:
3204       this.isEsc              = ESnormal;                               break;
3205     }
3206     break;
3207   }
3208   return lineBuf;
3209 };
3210
3211 VT100.prototype.renderString = function(s, showCursor) {
3212   // We try to minimize the number of DOM operations by coalescing individual
3213   // characters into strings. This is a significant performance improvement.
3214   var incX = s.length;
3215   if (incX > this.terminalWidth - this.cursorX) {
3216     incX   = this.terminalWidth - this.cursorX;
3217     if (incX <= 0) {
3218       return;
3219     }
3220     s      = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3221   }
3222   if (showCursor) {
3223     // Minimize the number of calls to putString(), by avoiding a direct
3224     // call to this.showCursor()
3225     this.cursor.style.visibility = '';
3226   }
3227   this.putString(this.cursorX, this.cursorY, s, this.style);
3228 };
3229
3230 VT100.prototype.vt100 = function(s) {
3231   this.cursorNeedsShowing = this.hideCursor();
3232   this.respondString      = '';
3233   var lineBuf             = '';
3234   for (var i = 0; i < s.length; i++) {
3235     var ch = s.charCodeAt(i);
3236     if (this.utfEnabled) {
3237       // Decode UTF8 encoded character
3238       if (ch > 0x7F) {
3239         if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3240           this.utfChar    = (this.utfChar << 6) | (ch & 0x3F);
3241           if (--this.utfCount <= 0) {
3242             if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3243               ch = 0xFFFD;
3244             } else {
3245               ch          = this.utfChar;
3246             }
3247           } else {
3248             continue;
3249           }
3250         } else {
3251           if ((ch & 0xE0) == 0xC0) {
3252             this.utfCount = 1;
3253             this.utfChar  = ch & 0x1F;
3254           } else if ((ch & 0xF0) == 0xE0) {
3255             this.utfCount = 2;
3256             this.utfChar  = ch & 0x0F;
3257           } else if ((ch & 0xF8) == 0xF0) {
3258             this.utfCount = 3;
3259             this.utfChar  = ch & 0x07;
3260           } else if ((ch & 0xFC) == 0xF8) {
3261             this.utfCount = 4;
3262             this.utfChar  = ch & 0x03;
3263           } else if ((ch & 0xFE) == 0xFC) {
3264             this.utfCount = 5;
3265             this.utfChar  = ch & 0x01;
3266           } else {
3267             this.utfCount = 0;
3268           }
3269           continue;
3270         }
3271       } else {
3272         this.utfCount     = 0;
3273       }
3274     }
3275     var isNormalCharacter =
3276       (ch >= 32 && ch <= 127 || ch >= 160 ||
3277        this.utfEnabled && ch >= 128 ||
3278        !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3279       (ch != 0x7F || this.dispCtrl);
3280     
3281     if (isNormalCharacter && this.isEsc == ESnormal) {
3282       if (ch < 256) {
3283         ch                = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3284       }
3285       if ((ch & 0xFF00) == 0xF000) {
3286         ch                = ch & 0xFF;
3287       } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3288         continue;
3289       }
3290       if (this.needWrap || this.insertMode) {
3291         if (lineBuf) {
3292           this.renderString(lineBuf);
3293           lineBuf         = '';
3294         }
3295       }
3296       if (this.needWrap) {
3297         this.cr(); this.lf();
3298       }
3299       if (this.insertMode) {
3300         this.scrollRegion(this.cursorX, this.cursorY,
3301                           this.terminalWidth - this.cursorX - 1, 1,
3302                           1, 0, this.style);
3303       }
3304       this.lastCharacter  = String.fromCharCode(ch);
3305       lineBuf            += this.lastCharacter;
3306       if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3307         this.needWrap     = this.autoWrapMode;
3308       }
3309     } else {
3310       if (lineBuf) {
3311         this.renderString(lineBuf);
3312         lineBuf           = '';
3313       }
3314       var expand          = this.doControl(ch);
3315       if (expand.length) {
3316         var r             = this.respondString;
3317         this.respondString= r + this.vt100(expand);
3318       }
3319     }
3320   }
3321   if (lineBuf) {
3322     this.renderString(lineBuf, this.cursorNeedsShowing);
3323   } else if (this.cursorNeedsShowing) {
3324     this.showCursor();
3325   }
3326   return this.respondString;
3327 };
3328
3329 VT100.prototype.Latin1Map = [
3330 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3331 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3332 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3333 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3334 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3335 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3336 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3337 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3338 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3339 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3340 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3341 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3342 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3343 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3344 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3345 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3346 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3347 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3348 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3349 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3350 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3351 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3352 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3353 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3354 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3355 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3356 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3357 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3358 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3359 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3360 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3361 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3362 ];
3363
3364 VT100.prototype.VT100GraphicsMap = [
3365 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3366 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3367 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3368 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3369 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3370 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3371 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3372 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3373 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3374 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3375 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3376 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3377 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3378 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3379 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3380 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3381 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3382 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3383 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3384 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3385 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3386 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3387 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3388 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3389 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3390 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3391 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3392 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3393 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3394 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3395 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3396 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3397 ];
3398
3399 VT100.prototype.CodePage437Map = [
3400 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3401 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3402 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3403 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3404 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3405 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3406 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3407 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3408 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3409 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3410 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3411 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3412 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3413 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3414 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3415 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3416 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3417 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3418 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3419 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3420 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3421 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3422 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3423 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3424 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3425 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3426 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3427 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3428 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3429 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3430 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3431 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3432 ];
3433
3434 VT100.prototype.DirectToFontMap = [
3435 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3436 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3437 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3438 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3439 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3440 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3441 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3442 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3443 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3444 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3445 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3446 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3447 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3448 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3449 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3450 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3451 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3452 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3453 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3454 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3455 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3456 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3457 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3458 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3459 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3460 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3461 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3462 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3463 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3464 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3465 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3466 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3467 ];
3468
3469 VT100.prototype.ctrlAction = [
3470   true,  false, false, false, false, false, false, true,
3471   true,  true,  true,  true,  true,  true,  true,  true,
3472   false, false, false, false, false, false, false, false,
3473   true,  false, true,  true,  false, false, false, false
3474 ];
3475
3476 VT100.prototype.ctrlAlways = [
3477   true,  false, false, false, false, false, false, false,
3478   true,  false, true,  false, true,  true,  true,  true,
3479   false, false, false, false, false, false, false, false,
3480   false, false, false, true,  false, false, false, false
3481 ];
3482
This page took 0.326916 seconds and 3 git commands to generate.