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