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