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