1// VT100.js -- JavaScript based terminal emulator
2// Copyright (C) 2008-2010 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 EStitle        17
84#define ESss2          18
85#define ESss3          19
86#define ESVTEtitle     20
87
88#define ATTR_DEFAULT   0x60F0
89#define ATTR_REVERSE   0x0100
90#define ATTR_UNDERLINE 0x0200
91#define ATTR_DIM       0x0400
92#define ATTR_BRIGHT    0x0800
93#define ATTR_BLINK     0x1000
94#define ATTR_DEF_FG    0x2000
95#define ATTR_DEF_BG    0x4000
96
97#define MOUSE_DOWN     0
98#define MOUSE_UP       1
99#define MOUSE_CLICK    2
100
101function VT100(container) {
102  if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
103    this.urlRE            = null;
104  } else {
105    this.urlRE            = new RegExp(
106    // Known URL protocol are "http", "https", and "ftp".
107    '(?:http|https|ftp)://' +
108
109    // Optionally allow username and passwords.
110    '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
111
112    // Hostname.
113    '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
114    '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
115    '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
116
117    // Port
118    '(?::[1-9][0-9]*)?' +
119
120    // Path.
121    '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$)' +
122    '[-a-zA-Z0-9@:%_\+.~#?&//=])*)*|' +
123
124    (linkifyURLs <= 1 ? '' :
125    // Also support URLs without a protocol (assume "http").
126    // Optional username and password.
127    '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
128
129    // Hostnames must end with a well-known top-level domain or must be
130    // numeric.
131    '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
132    'localhost|' +
133    '(?:(?!-)' +
134        '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
135    '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
136    'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
137    'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
138    'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
139    'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
140    'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
141    'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
142    'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
143    'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
144    'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
145    'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
146    'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
147    'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
148
149    // Port
150    '(?::[1-9][0-9]{0,4})?' +
151
152    // Path.
153    '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$)' +
154    '[-a-zA-Z0-9@:%_\+.~#?&//=])*)*|') +
155
156    // In addition, support e-mail address. Optionally, recognize "mailto:"
157    '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
158
159    // Username:
160    '[-_.+a-zA-Z0-9]+@' +
161
162    // Hostname.
163    '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
164    '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
165    'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
166    'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
167    'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
168    'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
169    'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
170    'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
171    'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
172    'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
173    'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
174    'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
175    'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
176    'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
177
178    // Optional arguments
179    '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
180  }
181  this.getUserSettings();
182  this.initializeElements(container);
183  this.maxScrollbackLines = 2000;
184  this.npar               = 0;
185  this.par                = [ ];
186  this.isQuestionMark     = false;
187  this.savedX             = [ ];
188  this.savedY             = [ ];
189  this.savedAttr          = [ ];
190  this.savedAttrFg        = [ ];
191  this.savedAttrBg        = [ ];
192  this.savedUseGMap       = 0;
193  this.savedGMap          = [ this.Latin1Map, this.VT100GraphicsMap,
194                              this.CodePage437Map, this.DirectToFontMap ];
195  this.savedValid         = [ ];
196  this.respondString      = '';
197  this.titleString        = '';
198  this.internalClipboard  = undefined;
199  this.reset(true);
200}
201
202VT100.prototype.reset = function(clearHistory) {
203  this.isEsc                                         = ESnormal;
204  this.needWrap                                      = false;
205  this.autoWrapMode                                  = true;
206  this.dispCtrl                                      = false;
207  this.toggleMeta                                    = false;
208  this.insertMode                                    = false;
209  this.applKeyMode                                   = false;
210  this.cursorKeyMode                                 = false;
211  this.crLfMode                                      = false;
212  this.offsetMode                                    = false;
213  this.mouseReporting                                = false;
214  this.printing                                      = false;
215  if (typeof this.printWin != 'undefined' &&
216      this.printWin && !this.printWin.closed) {
217    this.printWin.close();
218  }
219  this.printWin                                      = null;
220  this.utfEnabled                                    = this.utfPreferred;
221  this.utfCount                                      = 0;
222  this.utfChar                                       = 0;
223  this.color                                         = 'ansiDef bgAnsiDef';
224  this.style                                         = '';
225  this.attr                                          = ATTR_DEFAULT;
226  this.attrFg                                        = false;
227  this.attrBg                                        = false;
228  this.useGMap                                       = 0;
229  this.GMap                                          = [ this.Latin1Map,
230                                                         this.VT100GraphicsMap,
231                                                         this.CodePage437Map,
232                                                         this.DirectToFontMap];
233  this.translate                                     = this.GMap[this.useGMap];
234  this.top                                           = 0;
235  this.bottom                                        = this.terminalHeight;
236  this.lastCharacter                                 = ' ';
237  this.userTabStop                                   = [ ];
238
239  if (clearHistory) {
240    for (var i = 0; i < 2; i++) {
241      while (this.console[i].firstChild) {
242        this.console[i].removeChild(this.console[i].firstChild);
243      }
244    }
245  }
246
247  this.enableAlternateScreen(false);
248
249  var wasCompressed                                  = false;
250  var transform                                      = this.getTransformName();
251  if (transform) {
252    for (var i = 0; i < 2; ++i) {
253      wasCompressed                  |= this.console[i].style[transform] != '';
254      this.console[i].style[transform]               = '';
255    }
256    this.cursor.style[transform]                     = '';
257    this.space.style[transform]                      = '';
258    if (transform == 'filter') {
259      this.console[this.currentScreen].style.width   = '';
260    }
261  }
262  this.scale                                         = 1.0;
263  if (wasCompressed) {
264    this.resizer();
265  }
266
267  this.gotoXY(0, 0);
268  this.showCursor();
269  this.isInverted                                    = false;
270  this.refreshInvertedState();
271  this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
272                   this.color, this.style);
273};
274
275VT100.prototype.addListener = function(elem, event, listener) {
276  try {
277    if (elem.addEventListener) {
278      elem.addEventListener(event, listener, false);
279    } else {
280      elem.attachEvent('on' + event, listener);
281    }
282  } catch (e) {
283  }
284};
285
286VT100.prototype.getUserSettings = function() {
287  // Compute hash signature to identify the entries in the userCSS menu.
288  // If the menu is unchanged from last time, default values can be
289  // looked up in a cookie associated with this page.
290  this.signature            = 3;
291  this.utfPreferred         = true;
292  this.visualBell           = typeof suppressAllAudio != 'undefined' &&
293                              suppressAllAudio;
294  this.autoprint            = true;
295  this.softKeyboard         = false;
296  this.blinkingCursor       = true;
297  this.disableAlt           = false;
298
299  if (navigator.platform.indexOf("Mac") != -1) {
300    this.disableAlt         = true;
301  }
302
303  // Enable soft keyboard icon on some clients by default.
304  if (navigator.userAgent.match(/iPad|iPhone|iPod/i) != null ||
305      navigator.userAgent.match(/PlayStation Vita|Kindle/i) != null) {
306    this.softKeyboard       = true;
307  }
308
309  if (this.visualBell) {
310    this.signature          = Math.floor(16807*this.signature + 1) %
311                                         ((1 << 31) - 1);
312  }
313  if (typeof userCSSList != 'undefined') {
314    for (var i = 0; i < userCSSList.length; ++i) {
315      var label             = userCSSList[i][0];
316      for (var j = 0; j < label.length; ++j) {
317        this.signature      = Math.floor(16807*this.signature+
318                                         label.charCodeAt(j)) %
319                                         ((1 << 31) - 1);
320      }
321      if (userCSSList[i][1]) {
322        this.signature      = Math.floor(16807*this.signature + 1) %
323                                         ((1 << 31) - 1);
324      }
325    }
326  }
327
328  var key                   = 'shellInABox=' + this.signature + ':';
329  var settings              = document.cookie.indexOf(key);
330  if (settings >= 0) {
331    settings                = document.cookie.substr(settings + key.length).
332                                                   replace(/([0-1]*).*/, "$1");
333    if (settings.length == 6 + (typeof userCSSList == 'undefined' ?
334                                0 : userCSSList.length)) {
335      this.utfPreferred     = settings.charAt(0) != '0';
336      this.visualBell       = settings.charAt(1) != '0';
337      this.autoprint        = settings.charAt(2) != '0';
338      this.softKeyboard     = settings.charAt(3) != '0';
339      this.blinkingCursor   = settings.charAt(4) != '0';
340      this.disableAlt       = settings.charAt(5) != '0';
341      if (typeof userCSSList != 'undefined') {
342        for (var i = 0; i < userCSSList.length; ++i) {
343          userCSSList[i][2] = settings.charAt(i + 6) != '0';
344        }
345      }
346    }
347  }
348  this.utfEnabled           = this.utfPreferred;
349};
350
351VT100.prototype.storeUserSettings = function() {
352  var settings  = 'shellInABox=' + this.signature + ':' +
353                  (this.utfEnabled     ? '1' : '0') +
354                  (this.visualBell     ? '1' : '0') +
355                  (this.autoprint      ? '1' : '0') +
356                  (this.softKeyboard   ? '1' : '0') +
357                  (this.blinkingCursor ? '1' : '0') +
358                  (this.disableAlt     ? '1' : '0');
359  if (typeof userCSSList != 'undefined') {
360    for (var i = 0; i < userCSSList.length; ++i) {
361      settings += userCSSList[i][2] ? '1' : '0';
362    }
363  }
364  var d         = new Date();
365  d.setDate(d.getDate() + 3653);
366  document.cookie = settings + ';expires=' + d.toGMTString();
367};
368
369VT100.prototype.initializeUserCSSStyles = function() {
370  this.usercssActions                    = [];
371  if (typeof userCSSList != 'undefined') {
372    var menu                             = '';
373    var group                            = '';
374    var wasSingleSel                     = 1;
375    var beginOfGroup                     = 0;
376    for (var i = 0; i <= userCSSList.length; ++i) {
377      if (i < userCSSList.length) {
378        var label                        = userCSSList[i][0];
379        var newGroup                     = userCSSList[i][1];
380        var enabled                      = userCSSList[i][2];
381
382        // Add user style sheet to document
383        var style                        = document.createElement('link');
384        style.setAttribute('id',           'usercss-' + i);
385        style.setAttribute('href',         'usercss-' + i + '.css');
386        style.setAttribute('rel',          'stylesheet');
387        style.setAttribute('type',         'text/css');
388        document.getElementsByTagName('head')[0].appendChild(style);
389
390        // If stylesheet needs to be disabled we need to do that from onload
391        // event, otherwise 'disabled' attribute will be ignored.
392        if (!enabled) {
393          if ('onload' in style) {
394            style.onload                     = function(style) {
395              return function () {
396                style.disabled               = true;
397              }
398            }(style);
399          } else {
400            // If onload event is not supported we will try to do it the old
401            // way. This also works sometimes, mosty in cases when browser
402            // already has cached version of stylesheet.
403            style.disabled                   = true;
404          }
405        }
406      }
407
408      // Add entry to menu
409      if (newGroup || i == userCSSList.length) {
410        if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
411          // The last group had multiple entries that are mutually exclusive;
412          // or the previous to last group did. In either case, we need to
413          // append a "<hr />" before we can add the last group to the menu.
414          menu                          += '<hr />';
415        }
416        wasSingleSel                     = i - beginOfGroup < 1;
417        menu                            += group;
418        group                            = '';
419
420        for (var j = beginOfGroup; j < i; ++j) {
421          this.usercssActions[this.usercssActions.length] =
422            function(vt100, current, begin, count) {
423
424              // Deselect all other entries in the group, then either select
425              // (for multiple entries in group) or toggle (for on/off entry)
426              // the current entry.
427              return function() {
428                var entry                = vt100.getChildById(vt100.menu,
429                                                              'beginusercss');
430                var i                    = -1;
431                var j                    = -1;
432                for (var c = count; c > 0; ++j) {
433                  if (entry.tagName == 'LI') {
434                    if (++i >= begin) {
435                      --c;
436                      var label          = vt100.usercss.childNodes[j];
437
438                      // Restore label to just the text content
439                      if (typeof label.textContent == 'undefined') {
440                        var s            = label.innerText;
441                        label.innerHTML  = '';
442                        label.appendChild(document.createTextNode(s));
443                      } else {
444                        label.textContent= label.textContent;
445                      }
446
447                      // User style sheets are numbered sequentially
448                      var sheet          = document.getElementById(
449                                                               'usercss-' + i);
450                      if (i == current) {
451                        if (count == 1) {
452                          sheet.disabled = !sheet.disabled;
453                        } else {
454                          sheet.disabled = false;
455                        }
456                        if (!sheet.disabled) {
457                          label.innerHTML= '<img src="enabled.gif" />' +
458                                           label.innerHTML;
459                        }
460                      } else {
461                        sheet.disabled   = true;
462                      }
463                      userCSSList[i][2]  = !sheet.disabled;
464                    }
465                  }
466                  entry                  = entry.nextSibling;
467                }
468
469                // If the font size changed, adjust cursor and line dimensions
470                this.cursor.style.cssText= '';
471                this.cursorWidth         = this.cursor.clientWidth;
472                this.cursorHeight        = this.lineheight.clientHeight;
473                for (i = 0; i < this.console.length; ++i) {
474                  for (var line = this.console[i].firstChild; line;
475                       line = line.nextSibling) {
476                    line.style.height    = this.cursorHeight + 'px';
477                  }
478                }
479                vt100.resizer();
480              };
481            }(this, j, beginOfGroup, i - beginOfGroup);
482        }
483
484        if (i == userCSSList.length) {
485          break;
486        }
487
488        beginOfGroup                     = i;
489      }
490      // Collect all entries in a group, before attaching them to the menu.
491      // This is necessary as we don't know whether this is a group of
492      // mutually exclusive options (which should be separated by "<hr />" on
493      // both ends), or whether this is a on/off toggle, which can be grouped
494      // together with other on/off options.
495      group                             +=
496        '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
497                 label +
498        '</li>';
499    }
500    this.usercss.innerHTML               = menu;
501  }
502};
503
504VT100.prototype.resetLastSelectedKey = function(e) {
505  var key                          = this.lastSelectedKey;
506  if (!key) {
507    return false;
508  }
509
510  var position                     = this.mousePosition(e);
511
512  // We don't get all the necessary events to reliably reselect a key
513  // if we moved away from it and then back onto it. We approximate the
514  // behavior by remembering the key until either we release the mouse
515  // button (we might never get this event if the mouse has since left
516  // the window), or until we move away too far.
517  var box                          = this.keyboard.firstChild;
518  if (position[0] <  box.offsetLeft + key.offsetWidth ||
519      position[1] <  box.offsetTop + key.offsetHeight ||
520      position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
521      position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
522      position[0] <  box.offsetLeft + key.offsetLeft - key.offsetWidth ||
523      position[1] <  box.offsetTop + key.offsetTop - key.offsetHeight ||
524      position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
525      position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
526    if (this.lastSelectedKey.className) log.console('reset: deselecting');
527    this.lastSelectedKey.className = '';
528    this.lastSelectedKey           = undefined;
529  }
530  return false;
531};
532
533VT100.prototype.showShiftState = function(state) {
534  var style              = document.getElementById('shift_state');
535  if (state) {
536    this.setTextContentRaw(style,
537                           '#vt100 #keyboard .shifted {' +
538                             'display: inline }' +
539                           '#vt100 #keyboard .unshifted {' +
540                             'display: none }');
541  } else {
542    this.setTextContentRaw(style, '');
543  }
544  var elems              = this.keyboard.getElementsByTagName('I');
545  for (var i = 0; i < elems.length; ++i) {
546    if (elems[i].id == '16') {
547      elems[i].className = state ? 'selected' : '';
548    }
549  }
550};
551
552VT100.prototype.showCtrlState = function(state) {
553  var ctrl         = this.getChildById(this.keyboard, '17' /* Ctrl */);
554  if (ctrl) {
555    ctrl.className = state ? 'selected' : '';
556  }
557};
558
559VT100.prototype.showAltState = function(state) {
560  var alt         = this.getChildById(this.keyboard, '18' /* Alt */);
561  if (alt) {
562    alt.className = state ? 'selected' : '';
563  }
564};
565
566VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
567  var fake      = [ ];
568  fake.charCode = ch;
569  fake.keyCode  = key;
570  fake.ctrlKey  = ctrl;
571  fake.shiftKey = shift;
572  fake.altKey   = alt;
573  fake.metaKey  = alt;
574  return this.handleKey(fake);
575};
576
577VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
578  if (elem == undefined) {
579    return;
580  }
581  if (ch == '\u00A0') {
582    // &nbsp; should be treated as a regular space character.
583    ch                                  = ' ';
584  }
585  if (ch != undefined && CH == undefined) {
586    // For letter keys, we automatically compute the uppercase character code
587    // from the lowercase one.
588    CH                                  = ch.toUpperCase();
589  }
590  if (KEY == undefined && key != undefined) {
591    // Most keys have identically key codes for both lowercase and uppercase
592    // keypresses. Normally, only function keys would have distinct key codes,
593    // whereas regular keys have character codes.
594    KEY                                 = key;
595  } else if (KEY == undefined && CH != undefined) {
596    // For regular keys, copy the character code to the key code.
597    KEY                                 = CH.charCodeAt(0);
598  }
599  if (key == undefined && ch != undefined) {
600    // For regular keys, copy the character code to the key code.
601    key                                 = ch.charCodeAt(0);
602  }
603  // Convert characters to numeric character codes. If the character code
604  // is undefined (i.e. this is a function key), set it to zero.
605  ch                                    = ch ? ch.charCodeAt(0) : 0;
606  CH                                    = CH ? CH.charCodeAt(0) : 0;
607
608  // Mouse down events high light the key. We also set lastSelectedKey. This
609  // is needed to that mouseout/mouseover can keep track of the key that
610  // is currently being clicked.
611  this.addListener(elem, 'mousedown',
612    function(vt100, elem, key) { return function(e) {
613      if ((e.which || e.button) == 1) {
614        if (vt100.lastSelectedKey) {
615          vt100.lastSelectedKey.className= '';
616        }
617        // Highlight the key while the mouse button is held down.
618        if (key == 16 /* Shift */) {
619          if (!elem.className != vt100.isShift) {
620            vt100.showShiftState(!vt100.isShift);
621          }
622        } else if (key == 17 /* Ctrl */) {
623          if (!elem.className != vt100.isCtrl) {
624            vt100.showCtrlState(!vt100.isCtrl);
625          }
626        } else if (key == 18 /* Alt */) {
627          if (!elem.className != vt100.isAlt) {
628            vt100.showAltState(!vt100.isAlt);
629          }
630        } else {
631          elem.className                  = 'selected';
632        }
633        vt100.lastSelectedKey             = elem;
634      }
635      return false; }; }(this, elem, key));
636  var clicked                           =
637    // Modifier keys update the state of the keyboard, but do not generate
638    // any key clicks that get forwarded to the application.
639    key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
640    function(vt100, elem) { return function(e) {
641      if (elem == vt100.lastSelectedKey) {
642        if (key == 16 /* Shift */) {
643          // The user clicked the Shift key
644          vt100.isShift                 = !vt100.isShift;
645          vt100.showShiftState(vt100.isShift);
646        } else if (key == 17 /* Ctrl */) {
647          vt100.isCtrl                  = !vt100.isCtrl;
648          vt100.showCtrlState(vt100.isCtrl);
649        } else if (key == 18 /* Alt */) {
650          vt100.isAlt                   = !vt100.isAlt;
651          vt100.showAltState(vt100.isAlt);
652        }
653        vt100.lastSelectedKey           = undefined;
654      }
655      if (vt100.lastSelectedKey) {
656        vt100.lastSelectedKey.className = '';
657        vt100.lastSelectedKey           = undefined;
658      }
659      return false; }; }(this, elem) :
660    // Regular keys generate key clicks, when the mouse button is released or
661    // when a mouse click event is received.
662    function(vt100, elem, ch, key, CH, KEY) { return function(e) {
663      if (vt100.lastSelectedKey) {
664        if (elem == vt100.lastSelectedKey) {
665          // The user clicked a key.
666          if (vt100.isShift) {
667            vt100.clickedKeyboard(e, elem, CH, KEY,
668                                  true, vt100.isCtrl, vt100.isAlt);
669          } else {
670            vt100.clickedKeyboard(e, elem, ch, key,
671                                  false, vt100.isCtrl, vt100.isAlt);
672          }
673          vt100.isShift                 = false;
674          vt100.showShiftState(false);
675          vt100.isCtrl                  = false;
676          vt100.showCtrlState(false);
677          vt100.isAlt                   = false;
678          vt100.showAltState(false);
679        }
680        vt100.lastSelectedKey.className = '';
681        vt100.lastSelectedKey           = undefined;
682      }
683      elem.className                    = '';
684      return false; }; }(this, elem, ch, key, CH, KEY);
685  this.addListener(elem, 'mouseup', clicked);
686  this.addListener(elem, 'click', clicked);
687
688  // When moving the mouse away from a key, check if any keys need to be
689  // deselected.
690  this.addListener(elem, 'mouseout',
691    function(vt100, elem, key) { return function(e) {
692      if (key == 16 /* Shift */) {
693        if (!elem.className == vt100.isShift) {
694          vt100.showShiftState(vt100.isShift);
695        }
696      } else if (key == 17 /* Ctrl */) {
697        if (!elem.className == vt100.isCtrl) {
698          vt100.showCtrlState(vt100.isCtrl);
699        }
700      } else if (key == 18 /* Alt */) {
701        if (!elem.className == vt100.isAlt) {
702          vt100.showAltState(vt100.isAlt);
703        }
704      } else if (elem.className) {
705        elem.className                  = '';
706        vt100.lastSelectedKey           = elem;
707      } else if (vt100.lastSelectedKey) {
708        vt100.resetLastSelectedKey(e);
709      }
710      return false; }; }(this, elem, key));
711
712  // When moving the mouse over a key, select it if the user is still holding
713  // the mouse button down (i.e. elem == lastSelectedKey)
714  this.addListener(elem, 'mouseover',
715    function(vt100, elem, key) { return function(e) {
716      if (elem == vt100.lastSelectedKey) {
717        if (key == 16 /* Shift */) {
718          if (!elem.className != vt100.isShift) {
719            vt100.showShiftState(!vt100.isShift);
720          }
721        } else if (key == 17 /* Ctrl */) {
722          if (!elem.className != vt100.isCtrl) {
723            vt100.showCtrlState(!vt100.isCtrl);
724          }
725        } else if (key == 18 /* Alt */) {
726          if (!elem.className != vt100.isAlt) {
727            vt100.showAltState(!vt100.isAlt);
728          }
729        } else if (!elem.className) {
730          elem.className                = 'selected';
731        }
732      } else {
733        vt100.resetLastSelectedKey(e);
734      }
735      return false; }; }(this, elem, key));
736};
737
738VT100.prototype.initializeKeyBindings = function(elem) {
739  if (elem) {
740    if (elem.nodeName == "I" || elem.nodeName == "B") {
741      if (elem.id) {
742        // Function keys. The Javascript keycode is part of the "id"
743        var i     = parseInt(elem.id);
744        if (i) {
745          // If the id does not parse as a number, it is not a keycode.
746          this.addKeyBinding(elem, undefined, i);
747        }
748      } else {
749        var child = elem.firstChild;
750        if (child) {
751          if (child.nodeName == "#text") {
752            // If the key only has a text node as a child, then it is a letter.
753            // Automatically compute the lower and upper case version of the
754            // key.
755            var text = this.getTextContent(child) ||
756                       this.getTextContent(elem);
757            this.addKeyBinding(elem, text.toLowerCase());
758          } else if (child.nextSibling) {
759            // If the key has two children, they are the lower and upper case
760            // character code, respectively.
761            this.addKeyBinding(elem, this.getTextContent(child), undefined,
762                               this.getTextContent(child.nextSibling));
763          }
764        }
765      }
766    }
767  }
768  // Recursively parse all other child nodes.
769  for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
770    this.initializeKeyBindings(elem);
771  }
772};
773
774VT100.prototype.initializeKeyboardButton = function() {
775  // Configure mouse event handlers for button that displays/hides keyboard
776  this.addListener(this.keyboardImage, 'click',
777    function(vt100) { return function(e) {
778      if (vt100.keyboard.style.display != '') {
779        if (vt100.reconnectBtn.style.visibility != '') {
780          vt100.initializeKeyboard();
781          vt100.showSoftKeyboard();
782        }
783      } else {
784        vt100.hideSoftKeyboard();
785        vt100.input.focus();
786      }
787      return false; }; }(this));
788
789  // Enable button that displays keyboard
790  if (this.softKeyboard) {
791    this.keyboardImage.style.visibility = 'visible';
792  }
793};
794
795VT100.prototype.initializeKeyboard = function() {
796  // Only need to initialize the keyboard the very first time. When doing so,
797  // copy the keyboard layout from the iframe.
798  if (this.keyboard.firstChild) {
799    return;
800  }
801  this.keyboard.innerHTML               =
802                                    this.layout.contentDocument.body.innerHTML;
803  var box                               = this.keyboard.firstChild;
804  this.hideSoftKeyboard();
805
806  // Configure mouse event handlers for on-screen keyboard
807  this.addListener(this.keyboard, 'click',
808    function(vt100) { return function(e) {
809      vt100.hideSoftKeyboard();
810      vt100.input.focus();
811      return false; }; }(this));
812  this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
813  this.addListener(box, 'click', this.cancelEvent);
814  this.addListener(box, 'mouseup',
815    function(vt100) { return function(e) {
816      if (vt100.lastSelectedKey) {
817        vt100.lastSelectedKey.className = '';
818        vt100.lastSelectedKey           = undefined;
819      }
820      return false; }; }(this));
821  this.addListener(box, 'mouseout',
822    function(vt100) { return function(e) {
823      return vt100.resetLastSelectedKey(e); }; }(this));
824  this.addListener(box, 'mouseover',
825    function(vt100) { return function(e) {
826      return vt100.resetLastSelectedKey(e); }; }(this));
827
828  // Configure SHIFT key behavior
829  var style                             = document.createElement('style');
830  var id                                = document.createAttribute('id');
831  id.nodeValue                          = 'shift_state';
832  style.setAttributeNode(id);
833  var type                              = document.createAttribute('type');
834  type.nodeValue                        = 'text/css';
835  style.setAttributeNode(type);
836  document.getElementsByTagName('head')[0].appendChild(style);
837
838  // Set up key bindings
839  this.initializeKeyBindings(box);
840};
841
842VT100.prototype.initializeElements = function(container) {
843  // If the necessary objects have not already been defined in the HTML
844  // page, create them now.
845  if (container) {
846    this.container             = container;
847  } else if (!(this.container  = document.getElementById('vt100'))) {
848    this.container             = document.createElement('div');
849    this.container.id          = 'vt100';
850    document.body.appendChild(this.container);
851  }
852
853  if (!this.getChildById(this.container, 'reconnect')   ||
854      !this.getChildById(this.container, 'menu')        ||
855      !this.getChildById(this.container, 'keyboard')    ||
856      !this.getChildById(this.container, 'kbd_button')  ||
857      !this.getChildById(this.container, 'kbd_img')     ||
858      !this.getChildById(this.container, 'layout')      ||
859      !this.getChildById(this.container, 'scrollable')  ||
860      !this.getChildById(this.container, 'console')     ||
861      !this.getChildById(this.container, 'alt_console') ||
862      !this.getChildById(this.container, 'ieprobe')     ||
863      !this.getChildById(this.container, 'padding')     ||
864      !this.getChildById(this.container, 'cursor')      ||
865      !this.getChildById(this.container, 'lineheight')  ||
866      !this.getChildById(this.container, 'usercss')     ||
867      !this.getChildById(this.container, 'space')       ||
868      !this.getChildById(this.container, 'input')       ||
869      !this.getChildById(this.container, 'cliphelper')) {
870    // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
871    // we might get a pointless warning that a suitable plugin is not yet
872    // installed. If in doubt, we'd rather just stay silent.
873    var embed                  = '';
874    try {
875      if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
876          'undefined') {
877        embed                  = typeof suppressAllAudio != 'undefined' &&
878                                 suppressAllAudio ? "" :
879        '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
880                       'id="beep_embed" ' +
881                       'src="beep.wav" ' +
882                       'autostart="false" ' +
883                       'volume="100" ' +
884                       'enablejavascript="true" ' +
885                       'type="audio/x-wav" ' +
886                       'height="16" ' +
887                       'width="200" ' +
888                       'style="position:absolute;left:-1000px;top:-1000px" />';
889      }
890    } catch (e) {
891    }
892
893    this.container.innerHTML   =
894                       '<div id="reconnect" style="visibility: hidden">' +
895                         '<input type="button" value="Connect" ' +
896                                'onsubmit="return false" />' +
897                       '</div>' +
898                       '<div id="cursize" style="visibility: hidden">' +
899                       '</div>' +
900                       '<div id="menu"></div>' +
901                       '<div id="keyboard" unselectable="on">' +
902                       '</div>' +
903                       '<div id="scrollable">' +
904                         '<table id="kbd_button">' +
905                           '<tr><td width="100%">&nbsp;</td>' +
906                           '<td><img id="kbd_img" src="keyboard.png" /></td>' +
907                           '<td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr>' +
908                         '</table>' +
909                         '<pre id="lineheight">&nbsp;</pre>' +
910                         '<pre id="console">' +
911                           '<pre></pre>' +
912                           '<div id="ieprobe"><span>&nbsp;</span></div>' +
913                         '</pre>' +
914                         '<pre id="alt_console" style="display: none"></pre>' +
915                         '<div id="padding"></div>' +
916                         '<pre id="cursor">&nbsp;</pre>' +
917                       '</div>' +
918                       '<div class="hidden">' +
919                         '<div id="usercss"></div>' +
920                         '<pre><div><span id="space"></span></div></pre>' +
921                         '<input type="text" id="input" autocorrect="off" autocapitalize="off" />' +
922                         '<input type="text" id="cliphelper" />' +
923                         (typeof suppressAllAudio != 'undefined' &&
924                          suppressAllAudio ? "" :
925                         embed + '<bgsound id="beep_bgsound" loop=1 />') +
926                          '<iframe id="layout" src="keyboard.html" />' +
927                        '</div>';
928  }
929
930  // Find the object used for playing the "beep" sound, if any.
931  if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
932    this.beeper                = undefined;
933  } else {
934    this.beeper                = this.getChildById(this.container,
935                                                   'beep_embed');
936    if (!this.beeper || !this.beeper.Play) {
937      this.beeper              = this.getChildById(this.container,
938                                                   'beep_bgsound');
939      if (!this.beeper || typeof this.beeper.src == 'undefined') {
940        this.beeper            = undefined;
941      }
942    }
943  }
944
945  // Initialize the variables for finding the text console and the
946  // cursor.
947  this.reconnectBtn            = this.getChildById(this.container,'reconnect');
948  this.curSizeBox              = this.getChildById(this.container, 'cursize');
949  this.menu                    = this.getChildById(this.container, 'menu');
950  this.keyboard                = this.getChildById(this.container, 'keyboard');
951  this.keyboardImage           = this.getChildById(this.container, 'kbd_img');
952  this.layout                  = this.getChildById(this.container, 'layout');
953  this.scrollable              = this.getChildById(this.container,
954                                                                 'scrollable');
955  this.lineheight              = this.getChildById(this.container,
956                                                                 'lineheight');
957  this.console                 =
958                          [ this.getChildById(this.container, 'console'),
959                            this.getChildById(this.container, 'alt_console') ];
960  var ieProbe                  = this.getChildById(this.container, 'ieprobe');
961  this.padding                 = this.getChildById(this.container, 'padding');
962  this.cursor                  = this.getChildById(this.container, 'cursor');
963  this.usercss                 = this.getChildById(this.container, 'usercss');
964  this.space                   = this.getChildById(this.container, 'space');
965  this.input                   = this.getChildById(this.container, 'input');
966  this.cliphelper              = this.getChildById(this.container,
967                                                                 'cliphelper');
968
969  // Add any user selectable style sheets to the menu
970  this.initializeUserCSSStyles();
971
972  // Remember the dimensions of a standard character glyph. We would
973  // expect that we could just check cursor.clientWidth/Height at any time,
974  // but it turns out that browsers sometimes invalidate these values
975  // (e.g. while displaying a print preview screen).
976  this.cursorWidth             = this.cursor.clientWidth;
977  this.cursorHeight            = this.lineheight.clientHeight;
978
979  // IE has a slightly different boxing model, that we need to compensate for
980  this.isIE                    = ieProbe.offsetTop > 1;
981  ieProbe                      = undefined;
982  this.console.innerHTML       = '';
983
984  // Determine if the terminal window is positioned at the beginning of the
985  // page, or if it is embedded somewhere else in the page. For full-screen
986  // terminals, automatically resize whenever the browser window changes.
987  var marginTop                = parseInt(this.getCurrentComputedStyle(
988                                          document.body, 'marginTop'));
989  var marginLeft               = parseInt(this.getCurrentComputedStyle(
990                                          document.body, 'marginLeft'));
991  var marginRight              = parseInt(this.getCurrentComputedStyle(
992                                          document.body, 'marginRight'));
993  var x                        = this.container.offsetLeft;
994  var y                        = this.container.offsetTop;
995  for (var parent = this.container; parent = parent.offsetParent; ) {
996    x                         += parent.offsetLeft;
997    y                         += parent.offsetTop;
998  }
999  // When zoom functionality is used, some browsers report container offsetWidth
1000  // wrong by one pixel. This is the reason why widthDiff can be 0 or -1 pixel,
1001  // and in this case variable isEmbedded is not set to true.
1002  var widthDiff                = ((window.innerWidth ||
1003                                   document.documentElement.clientWidth ||
1004                                   document.body.clientWidth) - marginRight)
1005                                 - (x + this.container.offsetWidth);
1006  this.isEmbedded              = marginTop != y ||
1007                                 marginLeft != x ||
1008                                 (widthDiff != 0 && widthDiff != -1);
1009  if (!this.isEmbedded) {
1010    // Some browsers generate resize events when the terminal is first
1011    // shown. Disable showing the size indicator until a little bit after
1012    // the terminal has been rendered the first time.
1013    this.indicateSize          = false;
1014    setTimeout(function(vt100) {
1015      return function() {
1016        vt100.indicateSize     = true;
1017      };
1018    }(this), 100);
1019    this.addListener(window, 'resize',
1020                     function(vt100) {
1021                       return function() {
1022                         vt100.hideContextMenu();
1023                         vt100.resizer();
1024                         vt100.showCurrentSize();
1025                        }
1026                      }(this));
1027
1028    // Hide extra scrollbars attached to window
1029    document.body.style.margin = '0px';
1030    try { document.body.style.overflow ='hidden'; } catch (e) { }
1031    try { document.body.oncontextmenu = function() {return false;};} catch(e){}
1032  }
1033
1034  // Set up onscreen soft keyboard
1035  this.initializeKeyboardButton();
1036
1037  // Hide context menu
1038  this.hideContextMenu();
1039
1040  // Add listener to reconnect button
1041  this.addListener(this.reconnectBtn.firstChild, 'click',
1042                   function(vt100) {
1043                     return function() {
1044                       var rc = vt100.reconnect();
1045                       vt100.input.focus();
1046                       return rc;
1047                     }
1048                   }(this));
1049
1050  // Add input listeners
1051  this.addListener(this.input, 'blur',
1052                   function(vt100) {
1053                     return function() { vt100.blurCursor(); } }(this));
1054  this.addListener(this.input, 'focus',
1055                   function(vt100) {
1056                     return function() { vt100.focusCursor(); } }(this));
1057  this.addListener(this.input, 'keydown',
1058                   function(vt100) {
1059                     return function(e) {
1060                       if (!e) e = window.event;
1061                       return vt100.keyDown(e); } }(this));
1062  this.addListener(this.input, 'keypress',
1063                   function(vt100) {
1064                     return function(e) {
1065                       if (!e) e = window.event;
1066                       return vt100.keyPressed(e); } }(this));
1067  this.addListener(this.input, 'keyup',
1068                   function(vt100) {
1069                     return function(e) {
1070                       if (!e) e = window.event;
1071                       return vt100.keyUp(e); } }(this));
1072
1073  // Attach listeners that move the focus to the <input> field. This way we
1074  // can make sure that we can receive keyboard input.
1075  var mouseEvent               = function(vt100, type) {
1076    return function(e) {
1077      if (!e) e = window.event;
1078      return vt100.mouseEvent(e, type);
1079    };
1080  };
1081  this.addListener(this.scrollable,'mousedown',mouseEvent(this, MOUSE_DOWN));
1082  this.addListener(this.scrollable,'mouseup',  mouseEvent(this, MOUSE_UP));
1083  this.addListener(this.scrollable,'click',    mouseEvent(this, MOUSE_CLICK));
1084
1085  // Initialize the blank terminal window.
1086  this.currentScreen           = 0;
1087  this.cursorX                 = 0;
1088  this.cursorY                 = 0;
1089  this.numScrollbackLines      = 0;
1090  this.top                     = 0;
1091  this.bottom                  = 0x7FFFFFFF;
1092  this.scale                   = 1.0;
1093  this.resizer();
1094  this.focusCursor();
1095  this.input.focus();
1096};
1097
1098VT100.prototype.getChildById = function(parent, id) {
1099  var nodeList = parent.all || parent.getElementsByTagName('*');
1100  if (typeof nodeList.namedItem == 'undefined') {
1101    for (var i = 0; i < nodeList.length; i++) {
1102      if (nodeList[i].id == id) {
1103        return nodeList[i];
1104      }
1105    }
1106    return null;
1107  } else {
1108    var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1109    return elem ? elem[0] || elem : null;
1110  }
1111};
1112
1113VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1114  if (typeof elem.currentStyle != 'undefined') {
1115    return elem.currentStyle[style];
1116  } else {
1117    return document.defaultView.getComputedStyle(elem, null)[style];
1118  }
1119};
1120
1121VT100.prototype.reconnect = function() {
1122  return false;
1123};
1124
1125VT100.prototype.showReconnect = function(state) {
1126  if (state) {
1127    this.hideSoftKeyboard();
1128    this.reconnectBtn.style.visibility = '';
1129  } else {
1130    this.reconnectBtn.style.visibility = 'hidden';
1131  }
1132};
1133
1134VT100.prototype.repairElements = function(console) {
1135  for (var line = console.firstChild; line; line = line.nextSibling) {
1136    if (!line.clientHeight) {
1137      var newLine = document.createElement(line.tagName);
1138      newLine.style.cssText       = line.style.cssText;
1139      newLine.className           = line.className;
1140      if (line.tagName == 'DIV') {
1141        for (var span = line.firstChild; span; span = span.nextSibling) {
1142          var newSpan             = document.createElement(span.tagName);
1143          newSpan.style.cssText   = span.style.cssText;
1144          newSpan.className       = span.className;
1145          this.setTextContent(newSpan, this.getTextContent(span));
1146          newLine.appendChild(newSpan);
1147        }
1148      } else {
1149        this.setTextContent(newLine, this.getTextContent(line));
1150      }
1151      line.parentNode.replaceChild(newLine, line);
1152      line                        = newLine;
1153    }
1154  }
1155};
1156
1157VT100.prototype.resized = function(w, h) {
1158};
1159
1160VT100.prototype.resizer = function() {
1161  // Hide onscreen soft keyboard
1162  this.hideSoftKeyboard();
1163
1164  // The cursor can get corrupted if the print-preview is displayed in Firefox.
1165  // Recreating it, will repair it.
1166  var newCursor                = document.createElement('pre');
1167  this.setTextContent(newCursor, ' ');
1168  newCursor.id                 = 'cursor';
1169  newCursor.style.cssText      = this.cursor.style.cssText;
1170  this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1171  if (!newCursor.clientHeight) {
1172    // Things are broken right now. This is probably because we are
1173    // displaying the print-preview. Just don't change any of our settings
1174    // until the print dialog is closed again.
1175    newCursor.parentNode.removeChild(newCursor);
1176    return;
1177  } else {
1178    // Swap the old broken cursor for the newly created one.
1179    this.cursor.parentNode.removeChild(this.cursor);
1180    this.cursor                = newCursor;
1181  }
1182
1183  // Really horrible things happen if the contents of the terminal changes
1184  // while the print-preview is showing. We get HTML elements that show up
1185  // in the DOM, but that do not take up any space. Find these elements and
1186  // try to fix them.
1187  this.repairElements(this.console[0]);
1188  this.repairElements(this.console[1]);
1189
1190  // Lock the cursor size to the size of a normal character. This helps with
1191  // characters that are taller/shorter than normal. Unfortunately, we will
1192  // still get confused if somebody enters a character that is wider/narrower
1193  // than normal. This can happen if the browser tries to substitute a
1194  // characters from a different font.
1195  if (this.cursorWidth > 0) {
1196    this.cursor.style.width    = this.cursorWidth  + 'px';
1197  }
1198  if (this.cursorHeight > 0) {
1199    this.cursor.style.height   = this.cursorHeight + 'px';
1200  }
1201
1202  // Adjust height for one pixel padding of the #vt100 element.
1203  // The latter is necessary to properly display the inactive cursor.
1204  var console                  = this.console[this.currentScreen];
1205  var height                   = (this.isEmbedded ? this.container.clientHeight
1206                                  : (window.innerHeight ||
1207                                     document.documentElement.clientHeight ||
1208                                     document.body.clientHeight))-1;
1209
1210  // Prevent ever growing console on some iOS clients.
1211  if (navigator.userAgent.match(/iPad|iPhone|iPod/i) != null) {
1212    height                    -= 1;
1213  }
1214
1215  var partial                  = height % this.cursorHeight;
1216  this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1217  this.padding.style.height    = (partial > 0 ? partial : 0) + 'px';
1218  var oldTerminalHeight        = this.terminalHeight;
1219  this.updateWidth();
1220  this.updateHeight();
1221
1222  // Clip the cursor to the visible screen.
1223  var cx                       = this.cursorX;
1224  var cy                       = this.cursorY + this.numScrollbackLines;
1225
1226  // The alternate screen never keeps a scroll back buffer.
1227  this.updateNumScrollbackLines();
1228  while (this.currentScreen && this.numScrollbackLines > 0) {
1229    console.removeChild(console.firstChild);
1230    this.numScrollbackLines--;
1231  }
1232  cy                          -= this.numScrollbackLines;
1233  if (cx < 0) {
1234    cx                         = 0;
1235  } else if (cx > this.terminalWidth) {
1236    cx                         = this.terminalWidth - 1;
1237    if (cx < 0) {
1238      cx                       = 0;
1239    }
1240  }
1241  if (cy < 0) {
1242    cy                         = 0;
1243  } else if (cy > this.terminalHeight) {
1244    cy                         = this.terminalHeight - 1;
1245    if (cy < 0) {
1246      cy                       = 0;
1247    }
1248  }
1249
1250  // Clip the scroll region to the visible screen.
1251  if (this.bottom > this.terminalHeight ||
1252      this.bottom == oldTerminalHeight) {
1253    this.bottom                = this.terminalHeight;
1254  }
1255  if (this.top >= this.bottom) {
1256    this.top                   = this.bottom-1;
1257    if (this.top < 0) {
1258      this.top                 = 0;
1259    }
1260  }
1261
1262  // Truncate lines, if necessary. Explicitly reposition cursor (this is
1263  // particularly important after changing the screen number), and reset
1264  // the scroll region to the default.
1265  this.truncateLines(this.terminalWidth);
1266  this.putString(cx, cy, '', undefined);
1267  this.scrollable.scrollTop    = this.numScrollbackLines *
1268                                 this.cursorHeight + 1;
1269
1270  // Update classNames for lines in the scrollback buffer
1271  var line                     = console.firstChild;
1272  for (var i = 0; i < this.numScrollbackLines; i++) {
1273    line.className             = 'scrollback';
1274    line                       = line.nextSibling;
1275  }
1276  while (line) {
1277    line.className             = '';
1278    line                       = line.nextSibling;
1279  }
1280
1281  // Reposition the reconnect button
1282  this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1283                                  this.scale -
1284                                  this.reconnectBtn.clientWidth)/2 + 'px';
1285  this.reconnectBtn.style.top  = (this.terminalHeight*this.cursorHeight-
1286                                  this.reconnectBtn.clientHeight)/2 + 'px';
1287
1288  // Send notification that the window size has been changed
1289  this.resized(this.terminalWidth, this.terminalHeight);
1290};
1291
1292VT100.prototype.showCurrentSize = function() {
1293  if (!this.indicateSize) {
1294    return;
1295  }
1296  this.curSizeBox.innerHTML             = '' + this.terminalWidth + 'x' +
1297                                               this.terminalHeight;
1298  this.curSizeBox.style.left            =
1299                                      (this.terminalWidth*this.cursorWidth/
1300                                       this.scale -
1301                                       this.curSizeBox.clientWidth)/2 + 'px';
1302  this.curSizeBox.style.top             =
1303                                      (this.terminalHeight*this.cursorHeight -
1304                                       this.curSizeBox.clientHeight)/2 + 'px';
1305  this.curSizeBox.style.visibility      = '';
1306  if (this.curSizeTimeout) {
1307    clearTimeout(this.curSizeTimeout);
1308  }
1309
1310  // Only show the terminal size for a short amount of time after resizing.
1311  // Then hide this information, again. Some browsers generate resize events
1312  // throughout the entire resize operation. This is nice, and we will show
1313  // the terminal size while the user is dragging the window borders.
1314  // Other browsers only generate a single event when the user releases the
1315  // mouse. In those cases, we can only show the terminal size once at the
1316  // end of the resize operation.
1317  this.curSizeTimeout                   = setTimeout(function(vt100) {
1318    return function() {
1319      vt100.curSizeTimeout              = null;
1320      vt100.curSizeBox.style.visibility = 'hidden';
1321    };
1322  }(this), 1000);
1323};
1324
1325VT100.prototype.selection = function() {
1326  try {
1327    return '' + (window.getSelection && window.getSelection() ||
1328                 document.selection && document.selection.type == 'Text' &&
1329                 document.selection.createRange().text || '');
1330  } catch (e) {
1331  }
1332  return '';
1333};
1334
1335VT100.prototype.cancelEvent = function(event) {
1336  try {
1337    // For non-IE browsers
1338    event.stopPropagation();
1339    event.preventDefault();
1340  } catch (e) {
1341  }
1342  try {
1343    // For IE
1344    event.cancelBubble = true;
1345    event.returnValue  = false;
1346    event.button       = 0;
1347    event.keyCode      = 0;
1348  } catch (e) {
1349  }
1350  return false;
1351};
1352
1353VT100.prototype.mousePosition = function(event) {
1354  var offsetX      = this.container.offsetLeft;
1355  var offsetY      = this.container.offsetTop;
1356  for (var e = this.container; e = e.offsetParent; ) {
1357    offsetX       += e.offsetLeft;
1358    offsetY       += e.offsetTop;
1359  }
1360  return [ event.clientX - offsetX,
1361           event.clientY - offsetY ];
1362};
1363
1364VT100.prototype.mouseEvent = function(event, type) {
1365  // If any text is currently selected, do not move the focus as that would
1366  // invalidate the selection.
1367  var selection    = this.selection();
1368  if ((type == MOUSE_UP || type == MOUSE_CLICK) && !selection.length) {
1369    this.input.focus();
1370  }
1371
1372  // Compute mouse position in characters.
1373  var position     = this.mousePosition(event);
1374  var x            = Math.floor(position[0] / this.cursorWidth);
1375  var y            = Math.floor((position[1] + this.scrollable.scrollTop) /
1376                                this.cursorHeight) - this.numScrollbackLines;
1377  var inside       = true;
1378  if (x >= this.terminalWidth) {
1379    x              = this.terminalWidth - 1;
1380    inside         = false;
1381  }
1382  if (x < 0) {
1383    x              = 0;
1384    inside         = false;
1385  }
1386  if (y >= this.terminalHeight) {
1387    y              = this.terminalHeight - 1;
1388    inside         = false;
1389  }
1390  if (y < 0) {
1391    y              = 0;
1392    inside         = false;
1393  }
1394
1395  // Compute button number and modifier keys.
1396  var button       = type != MOUSE_DOWN ? 3 :
1397                     typeof event.pageX != 'undefined' ? event.button :
1398                     [ undefined, 0, 2, 0, 1, 0, 1, 0  ][event.button];
1399  if (button != undefined) {
1400    if (event.shiftKey) {
1401      button      |= 0x04;
1402    }
1403    if (event.altKey || event.metaKey) {
1404      button      |= 0x08;
1405    }
1406    if (event.ctrlKey) {
1407      button      |= 0x10;
1408    }
1409  }
1410
1411  // Report mouse events if they happen inside of the current screen and
1412  // with the SHIFT key unpressed. Both of these restrictions do not apply
1413  // for button releases, as we always want to report those.
1414  if (this.mouseReporting && !selection.length &&
1415      (type != MOUSE_DOWN || !event.shiftKey)) {
1416    if (inside || type != MOUSE_DOWN) {
1417      if (button != undefined) {
1418        var report = '\u001B[M' + String.fromCharCode(button + 32) +
1419                                  String.fromCharCode(x      + 33) +
1420                                  String.fromCharCode(y      + 33);
1421        if (type != MOUSE_CLICK) {
1422          this.keysPressed(report);
1423        }
1424
1425        // If we reported the event, stop propagating it (not sure, if this
1426        // actually works on most browsers; blocking the global "oncontextmenu"
1427        // even is still necessary).
1428        return this.cancelEvent(event);
1429      }
1430    }
1431  }
1432
1433  // Bring up context menu.
1434  if (button == 2 && !event.shiftKey) {
1435    if (type == MOUSE_DOWN) {
1436      this.showContextMenu(position[0], position[1]);
1437    }
1438    return this.cancelEvent(event);
1439  }
1440
1441  // Simulate middle click pasting from inside of current window. Note that
1442  // pasting content from other programs will not work in this way, since we
1443  // don't have access to native clipboard.
1444  if ((event.which || event.button) == 2 && selection.length) {
1445    if (type == MOUSE_UP) {
1446      // Use timeout to prevent double paste on Chrome/Linux.
1447      setTimeout(function (vt100) {
1448        return function() {
1449          vt100.keysPressed(selection);
1450          vt100.input.focus();
1451        }
1452      }(this), 10);
1453    }
1454    if (type == MOUSE_DOWN) {
1455      // Prevent middle click scroll on Windows systems.
1456      return this.cancelEvent(event);
1457    }
1458  }
1459
1460  if (this.mouseReporting) {
1461    try {
1462      event.shiftKey         = false;
1463    } catch (e) {
1464    }
1465  }
1466
1467  return true;
1468};
1469
1470VT100.prototype.replaceChar = function(s, ch, repl) {
1471  for (var i = -1;;) {
1472    i = s.indexOf(ch, i + 1);
1473    if (i < 0) {
1474      break;
1475    }
1476    s = s.substr(0, i) + repl + s.substr(i + 1);
1477  }
1478  return s;
1479};
1480
1481VT100.prototype.htmlEscape = function(s) {
1482  return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1483                s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;'), ' ', '\u00A0');
1484};
1485
1486VT100.prototype.getTextContent = function(elem) {
1487  return elem.textContent ||
1488         (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1489};
1490
1491VT100.prototype.setTextContentRaw = function(elem, s) {
1492  // Updating the content of an element is an expensive operation. It actually
1493  // pays off to first check whether the element is still unchanged.
1494  if (typeof elem.textContent == 'undefined') {
1495    if (elem.innerText != s) {
1496      try {
1497        elem.innerText = s;
1498      } catch (e) {
1499        // Very old versions of IE do not allow setting innerText. Instead,
1500        // remove all children, by setting innerHTML and then set the text
1501        // using DOM methods.
1502        elem.innerHTML = '';
1503        elem.appendChild(document.createTextNode(
1504                                          this.replaceChar(s, ' ', '\u00A0')));
1505      }
1506    }
1507  } else {
1508    if (elem.textContent != s) {
1509      elem.textContent = s;
1510    }
1511  }
1512};
1513
1514VT100.prototype.setTextContent = function(elem, s) {
1515  // Check if we find any URLs in the text. If so, automatically convert them
1516  // to links.
1517  if (this.urlRE && this.urlRE.test(s)) {
1518    var inner          = '';
1519    for (;;) {
1520      var consumed = 0;
1521      if (RegExp.leftContext != null) {
1522        inner         += this.htmlEscape(RegExp.leftContext);
1523        consumed      += RegExp.leftContext.length;
1524      }
1525      var url          = this.htmlEscape(RegExp.lastMatch);
1526      var fullUrl      = url;
1527
1528      // If no protocol was specified, try to guess a reasonable one.
1529      if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1530          url.indexOf('ftp://')  < 0 && url.indexOf('mailto:')  < 0) {
1531        var slash      = url.indexOf('/');
1532        var at         = url.indexOf('@');
1533        var question   = url.indexOf('?');
1534        if (at > 0 &&
1535            (at < question || question < 0) &&
1536            (slash < 0 || (question > 0 && slash > question))) {
1537          fullUrl      = 'mailto:' + url;
1538        } else {
1539          fullUrl      = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1540                          url;
1541        }
1542      }
1543
1544      inner           += '<a target="vt100Link" href="' + fullUrl +
1545                         '">' + url + '</a>';
1546      consumed        += RegExp.lastMatch.length;
1547      s                = s.substr(consumed);
1548      if (!this.urlRE.test(s)) {
1549        if (RegExp.rightContext != null) {
1550          inner       += this.htmlEscape(RegExp.rightContext);
1551        }
1552        break;
1553      }
1554    }
1555    elem.innerHTML     = inner;
1556    return;
1557  }
1558
1559  this.setTextContentRaw(elem, s);
1560};
1561
1562VT100.prototype.insertBlankLine = function(y, color, style) {
1563  // Insert a blank line a position y. This method ignores the scrollback
1564  // buffer. The caller has to add the length of the scrollback buffer to
1565  // the position, if necessary.
1566  // If the position is larger than the number of current lines, this
1567  // method just adds a new line right after the last existing one. It does
1568  // not add any missing lines in between. It is the caller's responsibility
1569  // to do so.
1570  if (!color) {
1571    color                = 'ansiDef bgAnsiDef';
1572  }
1573  if (!style) {
1574    style                = '';
1575  }
1576  var line;
1577  if (color != 'ansiDef bgAnsiDef' && !style) {
1578    line                 = document.createElement('pre');
1579    this.setTextContent(line, '\n');
1580  } else {
1581    line                 = document.createElement('div');
1582    var span             = document.createElement('span');
1583    span.style.cssText   = style;
1584    span.className       = color;
1585    this.setTextContent(span, this.spaces(this.terminalWidth));
1586    line.appendChild(span);
1587  }
1588  line.style.height      = this.cursorHeight + 'px';
1589  var console            = this.console[this.currentScreen];
1590  if (console.childNodes.length > y) {
1591    console.insertBefore(line, console.childNodes[y]);
1592  } else {
1593    console.appendChild(line);
1594  }
1595};
1596
1597VT100.prototype.updateWidth = function() {
1598  //  if the cursorWidth is zero, something is wrong. Try to get it some other way.
1599  if (this.cursorWidth <= 0) {
1600    if (this.cursor.clientWidth <= 0) {
1601      // Rats, this.cursor.clientWidth is zero too. Best guess?
1602      this.terminalWidth = 80;
1603    } else {
1604      // update the size.
1605      this.cursorWidth = this.cursor.clientWidth;
1606      this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/this.cursorWidth*this.scale);
1607    }
1608  } else {
1609    if ("ActiveXObject" in window)
1610      this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/this.cursorWidth*this.scale*0.95);
1611    else
1612      this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/this.cursorWidth*this.scale);
1613  }
1614
1615  return this.terminalWidth;
1616};
1617
1618VT100.prototype.updateHeight = function() {
1619  // We want to be able to display either a terminal window that fills the
1620  // entire browser window, or a terminal window that is contained in a
1621  // <div> which is embededded somewhere in the web page.
1622  if (this.isEmbedded) {
1623    // Embedded terminal. Use size of the containing <div> (id="vt100").
1624    this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1625                                     this.cursorHeight);
1626  } else {
1627    // Use the full browser window.
1628    this.terminalHeight = Math.floor(((window.innerHeight ||
1629                                       document.documentElement.clientHeight ||
1630                                       document.body.clientHeight)-1)/
1631                                     this.cursorHeight);
1632  }
1633  return this.terminalHeight;
1634};
1635
1636VT100.prototype.updateNumScrollbackLines = function() {
1637  var scrollback          = Math.floor(
1638                                this.console[this.currentScreen].offsetHeight /
1639                                this.cursorHeight) -
1640                            this.terminalHeight;
1641  this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1642  return this.numScrollbackLines;
1643};
1644
1645VT100.prototype.truncateLines = function(width) {
1646  if (width < 0) {
1647    width             = 0;
1648  }
1649  for (var line = this.console[this.currentScreen].firstChild; line;
1650       line = line.nextSibling) {
1651    if (line.tagName == 'DIV') {
1652      var x           = 0;
1653
1654      // Traverse current line and truncate it once we saw "width" characters
1655      for (var span = line.firstChild; span;
1656           span = span.nextSibling) {
1657        var s         = this.getTextContent(span);
1658        var l         = s.length;
1659        if (x + l > width) {
1660          this.setTextContent(span, s.substr(0, width - x));
1661          while (span.nextSibling) {
1662            line.removeChild(line.lastChild);
1663          }
1664          break;
1665        }
1666        x            += l;
1667      }
1668      // Prune white space from the end of the current line
1669      var span       = line.lastChild;
1670      while (span &&
1671             span.className == 'ansiDef bgAnsiDef' &&
1672             !span.style.cssText.length) {
1673        // Scan backwards looking for first non-space character
1674        var s         = this.getTextContent(span);
1675        for (var i = s.length; i--; ) {
1676          if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1677            if (i+1 != s.length) {
1678              this.setTextContent(s.substr(0, i+1));
1679            }
1680            span      = null;
1681            break;
1682          }
1683        }
1684        if (span) {
1685          var sibling = span;
1686          span        = span.previousSibling;
1687          if (span) {
1688            // Remove blank <span>'s from end of line
1689            line.removeChild(sibling);
1690          } else {
1691            // Remove entire line (i.e. <div>), if empty
1692            var blank = document.createElement('pre');
1693            blank.style.height = this.cursorHeight + 'px';
1694            this.setTextContent(blank, '\n');
1695            line.parentNode.replaceChild(blank, line);
1696          }
1697        }
1698      }
1699    }
1700  }
1701};
1702
1703VT100.prototype.putString = function(x, y, text, color, style) {
1704  if (!color) {
1705    color                           = 'ansiDef bgAnsiDef';
1706  }
1707  if (!style) {
1708    style                           = '';
1709  }
1710  var yIdx                          = y + this.numScrollbackLines;
1711  var line;
1712  var sibling;
1713  var s;
1714  var span;
1715  var xPos                          = 0;
1716  var console                       = this.console[this.currentScreen];
1717  if (!text.length && (yIdx >= console.childNodes.length ||
1718                       console.childNodes[yIdx].tagName != 'DIV')) {
1719    // Positioning cursor to a blank location
1720    span                            = null;
1721  } else {
1722    // Create missing blank lines at end of page
1723    while (console.childNodes.length <= yIdx) {
1724      // In order to simplify lookups, we want to make sure that each line
1725      // is represented by exactly one element (and possibly a whole bunch of
1726      // children).
1727      // For non-blank lines, we can create a <div> containing one or more
1728      // <span>s. For blank lines, this fails as browsers tend to optimize them
1729      // away. But fortunately, a <pre> tag containing a newline character
1730      // appears to work for all browsers (a &nbsp; would also work, but then
1731      // copying from the browser window would insert superfluous spaces into
1732      // the clipboard).
1733      this.insertBlankLine(yIdx);
1734    }
1735    line                            = console.childNodes[yIdx];
1736
1737    // If necessary, promote blank '\n' line to a <div> tag
1738    if (line.tagName != 'DIV') {
1739      var div                       = document.createElement('div');
1740      div.style.height              = this.cursorHeight + 'px';
1741      div.innerHTML                 = '<span></span>';
1742      console.replaceChild(div, line);
1743      line                          = div;
1744    }
1745
1746    // Scan through list of <span>'s until we find the one where our text
1747    // starts
1748    span                            = line.firstChild;
1749    var len;
1750    while (span.nextSibling && xPos < x) {
1751      len                           = this.getTextContent(span).length;
1752      if (xPos + len > x) {
1753        break;
1754      }
1755      xPos                         += len;
1756      span                          = span.nextSibling;
1757    }
1758
1759    if (text.length) {
1760      // If current <span> is not long enough, pad with spaces or add new
1761      // span
1762      s                             = this.getTextContent(span);
1763      var oldColor                  = span.className;
1764      var oldStyle                  = span.style.cssText;
1765      if (xPos + s.length < x) {
1766        if (oldColor != 'ansiDef bgAnsiDef' || oldStyle != '') {
1767          span                      = document.createElement('span');
1768          line.appendChild(span);
1769          span.className            = 'ansiDef bgAnsiDef';
1770          span.style.cssText        = '';
1771          oldColor                  = 'ansiDef bgAnsiDef';
1772          oldStyle                  = '';
1773          xPos                     += s.length;
1774          s                         = '';
1775        }
1776        do {
1777          s                        += ' ';
1778        } while (xPos + s.length < x);
1779      }
1780
1781      // If styles do not match, create a new <span>
1782      var del                       = text.length - s.length + x - xPos;
1783      if (oldColor != color ||
1784          (oldStyle != style && (oldStyle || style))) {
1785        if (xPos == x) {
1786          // Replacing text at beginning of existing <span>
1787          if (text.length >= s.length) {
1788            // New text is equal or longer than existing text
1789            s                       = text;
1790          } else {
1791            // Insert new <span> before the current one, then remove leading
1792            // part of existing <span>, adjust style of new <span>, and finally
1793            // set its contents
1794            sibling                 = document.createElement('span');
1795            line.insertBefore(sibling, span);
1796            this.setTextContent(span, s.substr(text.length));
1797            span                    = sibling;
1798            s                       = text;
1799          }
1800        } else {
1801          // Replacing text some way into the existing <span>
1802          var remainder             = s.substr(x + text.length - xPos);
1803          this.setTextContent(span, s.substr(0, x - xPos));
1804          xPos                      = x;
1805          sibling                   = document.createElement('span');
1806          if (span.nextSibling) {
1807            line.insertBefore(sibling, span.nextSibling);
1808            span                    = sibling;
1809            if (remainder.length) {
1810              sibling               = document.createElement('span');
1811              sibling.className     = oldColor;
1812              sibling.style.cssText = oldStyle;
1813              this.setTextContent(sibling, remainder);
1814              line.insertBefore(sibling, span.nextSibling);
1815            }
1816          } else {
1817            line.appendChild(sibling);
1818            span                    = sibling;
1819            if (remainder.length) {
1820              sibling               = document.createElement('span');
1821              sibling.className     = oldColor;
1822              sibling.style.cssText = oldStyle;
1823              this.setTextContent(sibling, remainder);
1824              line.appendChild(sibling);
1825            }
1826          }
1827          s                         = text;
1828        }
1829        span.className              = color;
1830        span.style.cssText          = style;
1831      } else {
1832        // Overwrite (partial) <span> with new text
1833        s                           = s.substr(0, x - xPos) +
1834          text +
1835          s.substr(x + text.length - xPos);
1836      }
1837      this.setTextContent(span, s);
1838
1839
1840      // Delete all subsequent <span>'s that have just been overwritten
1841      sibling                       = span.nextSibling;
1842      while (del > 0 && sibling) {
1843        s                           = this.getTextContent(sibling);
1844        len                         = s.length;
1845        if (len <= del) {
1846          line.removeChild(sibling);
1847          del                      -= len;
1848          sibling                   = span.nextSibling;
1849        } else {
1850          this.setTextContent(sibling, s.substr(del));
1851          break;
1852        }
1853      }
1854
1855      // Merge <span> with next sibling, if styles are identical
1856      if (sibling && span.className == sibling.className &&
1857          span.style.cssText == sibling.style.cssText) {
1858        this.setTextContent(span,
1859                            this.getTextContent(span) +
1860                            this.getTextContent(sibling));
1861        line.removeChild(sibling);
1862      }
1863    }
1864  }
1865
1866  // Position cursor
1867  this.cursorX                      = x + text.length;
1868  if (this.cursorX >= this.terminalWidth) {
1869    this.cursorX                    = this.terminalWidth - 1;
1870    if (this.cursorX < 0) {
1871      this.cursorX                  = 0;
1872    }
1873  }
1874  var pixelX                        = -1;
1875  var pixelY                        = -1;
1876  if (!this.cursor.style.visibility) {
1877    var idx                         = this.cursorX - xPos;
1878    if (span) {
1879      // If we are in a non-empty line, take the cursor Y position from the
1880      // other elements in this line. If dealing with broken, non-proportional
1881      // fonts, this is likely to yield better results.
1882      pixelY                        = span.offsetTop +
1883                                      span.offsetParent.offsetTop;
1884      s                             = this.getTextContent(span);
1885      var nxtIdx                    = idx - s.length;
1886      if (nxtIdx < 0) {
1887        this.setTextContent(this.cursor, s.charAt(idx));
1888        pixelX                      = span.offsetLeft +
1889                                      idx*span.offsetWidth / s.length;
1890      } else {
1891        if (nxtIdx == 0) {
1892          pixelX                    = span.offsetLeft + span.offsetWidth;
1893        }
1894        if (span.nextSibling) {
1895          s                         = this.getTextContent(span.nextSibling);
1896          this.setTextContent(this.cursor, s.charAt(nxtIdx));
1897          if (pixelX < 0) {
1898            pixelX                  = span.nextSibling.offsetLeft +
1899                                      nxtIdx*span.offsetWidth / s.length;
1900          }
1901        } else {
1902          this.setTextContent(this.cursor, ' ');
1903        }
1904      }
1905    } else {
1906      this.setTextContent(this.cursor, ' ');
1907    }
1908  }
1909  if (pixelX >= 0) {
1910    this.cursor.style.left          = (pixelX + (this.isIE ? 1 : 0))/
1911                                      this.scale + 'px';
1912  } else {
1913    this.setTextContent(this.space, this.spaces(this.cursorX));
1914    this.cursor.style.left          = (this.space.offsetWidth +
1915                                       console.offsetLeft)/this.scale + 'px';
1916  }
1917  this.cursorY                      = yIdx - this.numScrollbackLines;
1918  if (pixelY >= 0) {
1919    this.cursor.style.top           = pixelY + 'px';
1920  } else {
1921    this.cursor.style.top           = yIdx*this.cursorHeight +
1922                                      console.offsetTop + 'px';
1923  }
1924
1925  if (text.length) {
1926    // Merge <span> with previous sibling, if styles are identical
1927    if ((sibling = span.previousSibling) &&
1928        span.className == sibling.className &&
1929        span.style.cssText == sibling.style.cssText) {
1930      this.setTextContent(span,
1931                          this.getTextContent(sibling) +
1932                          this.getTextContent(span));
1933      line.removeChild(sibling);
1934    }
1935
1936    // Prune white space from the end of the current line
1937    span                            = line.lastChild;
1938    while (span &&
1939           span.className == 'ansiDef bgAnsiDef' &&
1940           !span.style.cssText.length) {
1941      // Scan backwards looking for first non-space character
1942      s                             = this.getTextContent(span);
1943      for (var i = s.length; i--; ) {
1944        if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1945          if (i+1 != s.length) {
1946            this.setTextContent(s.substr(0, i+1));
1947          }
1948          span                      = null;
1949          break;
1950        }
1951      }
1952      if (span) {
1953        sibling                     = span;
1954        span                        = span.previousSibling;
1955        if (span) {
1956          // Remove blank <span>'s from end of line
1957          line.removeChild(sibling);
1958        } else {
1959          // Remove entire line (i.e. <div>), if empty
1960          var blank                 = document.createElement('pre');
1961          blank.style.height        = this.cursorHeight + 'px';
1962          this.setTextContent(blank, '\n');
1963          line.parentNode.replaceChild(blank, line);
1964        }
1965      }
1966    }
1967  }
1968};
1969
1970VT100.prototype.gotoXY = function(x, y) {
1971  if (x >= this.terminalWidth) {
1972    x           = this.terminalWidth - 1;
1973  }
1974  if (x < 0) {
1975    x           = 0;
1976  }
1977  var minY, maxY;
1978  if (this.offsetMode) {
1979    minY        = this.top;
1980    maxY        = this.bottom;
1981  } else {
1982    minY        = 0;
1983    maxY        = this.terminalHeight;
1984  }
1985  if (y >= maxY) {
1986    y           = maxY - 1;
1987  }
1988  if (y < minY) {
1989    y           = minY;
1990  }
1991  this.putString(x, y, '', undefined);
1992  this.needWrap = false;
1993};
1994
1995VT100.prototype.gotoXaY = function(x, y) {
1996  this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1997};
1998
1999VT100.prototype.refreshInvertedState = function() {
2000  if (this.isInverted) {
2001    this.scrollable.className += ' inverted';
2002  } else {
2003    this.scrollable.className = this.scrollable.className.
2004                                                     replace(/ *inverted/, '');
2005  }
2006};
2007
2008VT100.prototype.enableAlternateScreen = function(state) {
2009  // Don't do anything, if we are already on the desired screen
2010  if ((state ? 1 : 0) == this.currentScreen) {
2011    // Calling the resizer is not actually necessary. But it is a good way
2012    // of resetting state that might have gotten corrupted.
2013    this.resizer();
2014    return;
2015  }
2016
2017  // We save the full state of the normal screen, when we switch away from it.
2018  // But for the alternate screen, no saving is necessary. We always reset
2019  // it when we switch to it.
2020  if (state) {
2021    this.saveCursor();
2022  }
2023
2024  // Display new screen, and initialize state (the resizer does that for us).
2025  this.currentScreen                                 = state ? 1 : 0;
2026  this.console[1-this.currentScreen].style.display   = 'none';
2027  this.console[this.currentScreen].style.display     = '';
2028
2029  // Select appropriate character pitch.
2030  var transform                                      = this.getTransformName();
2031  if (transform) {
2032    if (state) {
2033      // Upon enabling the alternate screen, we switch to 80 column mode. But
2034      // upon returning to the regular screen, we restore the mode that was
2035      // in effect previously.
2036      this.console[1].style[transform]               = '';
2037    }
2038    var style                                        =
2039                             this.console[this.currentScreen].style[transform];
2040    this.cursor.style[transform]                     = style;
2041    this.space.style[transform]                      = style;
2042    this.scale                                       = style == '' ? 1.0:1.65;
2043    if (transform == 'filter') {
2044       this.console[this.currentScreen].style.width  = style == '' ? '165%':'';
2045    }
2046  }
2047  this.resizer();
2048
2049  // If we switched to the alternate screen, reset it completely. Otherwise,
2050  // restore the saved state.
2051  if (state) {
2052    this.gotoXY(0, 0);
2053    this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
2054  } else {
2055    this.restoreCursor();
2056  }
2057};
2058
2059VT100.prototype.hideCursor = function() {
2060  var hidden = this.cursor.style.visibility == 'hidden';
2061  if (!hidden) {
2062    this.cursor.style.visibility = 'hidden';
2063    return true;
2064  }
2065  return false;
2066};
2067
2068VT100.prototype.showCursor = function(x, y) {
2069  if (this.cursor.style.visibility) {
2070    this.cursor.style.visibility = '';
2071    this.putString(x == undefined ? this.cursorX : x,
2072                   y == undefined ? this.cursorY : y,
2073                   '', undefined);
2074    return true;
2075  }
2076  return false;
2077};
2078
2079VT100.prototype.scrollBack = function() {
2080  var i                     = this.scrollable.scrollTop -
2081                              this.scrollable.clientHeight;
2082  this.scrollable.scrollTop = i < 0 ? 0 : i;
2083};
2084
2085VT100.prototype.scrollFore = function() {
2086  var i                     = this.scrollable.scrollTop +
2087                              this.scrollable.clientHeight;
2088  this.scrollable.scrollTop = i > this.numScrollbackLines *
2089                                  this.cursorHeight + 1
2090                              ? this.numScrollbackLines *
2091                                this.cursorHeight + 1
2092                              : i;
2093};
2094
2095VT100.prototype.spaces = function(i) {
2096  var s = '';
2097  while (i-- > 0) {
2098    s += ' ';
2099  }
2100  return s;
2101};
2102
2103VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2104  w         += x;
2105  if (x < 0) {
2106    x        = 0;
2107  }
2108  if (w > this.terminalWidth) {
2109    w        = this.terminalWidth;
2110  }
2111  if ((w    -= x) <= 0) {
2112    return;
2113  }
2114  h         += y;
2115  if (y < 0) {
2116    y        = 0;
2117  }
2118  if (h > this.terminalHeight) {
2119    h        = this.terminalHeight;
2120  }
2121  if ((h    -= y) <= 0) {
2122    return;
2123  }
2124
2125  // Special case the situation where we clear the entire screen, and we do
2126  // not have a scrollback buffer. In that case, we should just remove all
2127  // child nodes.
2128  if (!this.numScrollbackLines &&
2129      w == this.terminalWidth && h == this.terminalHeight &&
2130      (color == undefined || color == 'ansiDef bgAnsiDef') && !style) {
2131    var console = this.console[this.currentScreen];
2132    while (console.lastChild) {
2133      console.removeChild(console.lastChild);
2134    }
2135    this.putString(this.cursorX, this.cursorY, '', undefined);
2136  } else {
2137    var hidden = this.hideCursor();
2138    var cx     = this.cursorX;
2139    var cy     = this.cursorY;
2140    var s      = this.spaces(w);
2141    for (var i = y+h; i-- > y; ) {
2142      this.putString(x, i, s, color, style);
2143    }
2144    hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2145  }
2146};
2147
2148VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2149  var text                            = [ ];
2150  var className                       = [ ];
2151  var style                           = [ ];
2152  var console                         = this.console[this.currentScreen];
2153  if (sY >= console.childNodes.length) {
2154    text[0]                           = this.spaces(w);
2155    className[0]                      = undefined;
2156    style[0]                          = undefined;
2157  } else {
2158    var line = console.childNodes[sY];
2159    if (line.tagName != 'DIV' || !line.childNodes.length) {
2160      text[0]                         = this.spaces(w);
2161      className[0]                    = undefined;
2162      style[0]                        = undefined;
2163    } else {
2164      var x                           = 0;
2165      for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2166        var s                         = this.getTextContent(span);
2167        var len                       = s.length;
2168        if (x + len > sX) {
2169          var o                       = sX > x ? sX - x : 0;
2170          text[text.length]           = s.substr(o, w);
2171          className[className.length] = span.className;
2172          style[style.length]         = span.style.cssText;
2173          w                          -= len - o;
2174        }
2175        x                            += len;
2176      }
2177      if (w > 0) {
2178        text[text.length]             = this.spaces(w);
2179        className[className.length]   = undefined;
2180        style[style.length]           = undefined;
2181      }
2182    }
2183  }
2184  var hidden                          = this.hideCursor();
2185  var cx                              = this.cursorX;
2186  var cy                              = this.cursorY;
2187  for (var i = 0; i < text.length; i++) {
2188    var color;
2189    if (className[i]) {
2190      color                           = className[i];
2191    } else {
2192      color                           = 'ansiDef bgAnsiDef';
2193    }
2194    this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2195    dX                               += text[i].length;
2196  }
2197  hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2198};
2199
2200VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2201                                        color, style) {
2202  var left             = incX < 0 ? -incX : 0;
2203  var right            = incX > 0 ?  incX : 0;
2204  var up               = incY < 0 ? -incY : 0;
2205  var down             = incY > 0 ?  incY : 0;
2206
2207  // Clip region against terminal size
2208  var dontScroll       = null;
2209  w                   += x;
2210  if (x < left) {
2211    x                  = left;
2212  }
2213  if (w > this.terminalWidth - right) {
2214    w                  = this.terminalWidth - right;
2215  }
2216  if ((w              -= x) <= 0) {
2217    dontScroll         = 1;
2218  }
2219  h                   += y;
2220  if (y < up) {
2221    y                  = up;
2222  }
2223  if (h > this.terminalHeight - down) {
2224    h                  = this.terminalHeight - down;
2225  }
2226  if ((h              -= y) < 0) {
2227    dontScroll         = 1;
2228  }
2229  if (!dontScroll) {
2230    if (style && style.indexOf('underline')) {
2231      // Different terminal emulators disagree on the attributes that
2232      // are used for scrolling. The consensus seems to be, never to
2233      // fill with underlined spaces. N.B. this is different from the
2234      // cases when the user blanks a region. User-initiated blanking
2235      // always fills with all of the current attributes.
2236      style            = style.replace(/text-decoration:underline;/, '');
2237    }
2238
2239    // Compute current scroll position
2240    var scrollPos      = this.numScrollbackLines -
2241                      (this.scrollable.scrollTop-1) / this.cursorHeight;
2242
2243    // Determine original cursor position. Hide cursor temporarily to avoid
2244    // visual artifacts.
2245    var hidden         = this.hideCursor();
2246    var cx             = this.cursorX;
2247    var cy             = this.cursorY;
2248    var console        = this.console[this.currentScreen];
2249
2250    if (!incX && !x && w == this.terminalWidth) {
2251      // Scrolling entire lines
2252      if (incY < 0) {
2253        // Scrolling up
2254        if (!this.currentScreen && y == -incY &&
2255            h == this.terminalHeight + incY) {
2256          // Scrolling up with adding to the scrollback buffer. This is only
2257          // possible if there are at least as many lines in the console,
2258          // as the terminal is high
2259          while (console.childNodes.length < this.terminalHeight) {
2260            this.insertBlankLine(this.terminalHeight);
2261          }
2262
2263          // Add new lines at bottom in order to force scrolling
2264          for (var i = 0; i < y; i++) {
2265            this.insertBlankLine(console.childNodes.length, color, style);
2266          }
2267
2268          // Adjust the number of lines in the scrollback buffer by
2269          // removing excess entries.
2270          this.updateNumScrollbackLines();
2271          while (this.numScrollbackLines >
2272                 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2273            console.removeChild(console.firstChild);
2274            this.numScrollbackLines--;
2275          }
2276
2277          // Mark lines in the scrollback buffer, so that they do not get
2278          // printed.
2279          for (var i = this.numScrollbackLines, j = -incY;
2280               i-- > 0 && j-- > 0; ) {
2281            console.childNodes[i].className = 'scrollback';
2282          }
2283        } else {
2284          // Scrolling up without adding to the scrollback buffer.
2285          for (var i = -incY;
2286               i-- > 0 &&
2287               console.childNodes.length >
2288               this.numScrollbackLines + y + incY; ) {
2289            console.removeChild(console.childNodes[
2290                                          this.numScrollbackLines + y + incY]);
2291          }
2292
2293          // If we used to have a scrollback buffer, then we must make sure
2294          // that we add back blank lines at the bottom of the terminal.
2295          // Similarly, if we are scrolling in the middle of the screen,
2296          // we must add blank lines to ensure that the bottom of the screen
2297          // does not move up.
2298          if (this.numScrollbackLines > 0 ||
2299              console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2300            for (var i = -incY; i-- > 0; ) {
2301              this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2302                                   color, style);
2303            }
2304          }
2305        }
2306      } else {
2307        // Scrolling down
2308        for (var i = incY;
2309             i-- > 0 &&
2310             console.childNodes.length > this.numScrollbackLines + y + h; ) {
2311          console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2312        }
2313        for (var i = incY; i--; ) {
2314          this.insertBlankLine(this.numScrollbackLines + y, color, style);
2315        }
2316      }
2317    } else {
2318      // Scrolling partial lines
2319      if (incY <= 0) {
2320        // Scrolling up or horizontally within a line
2321        for (var i = y + this.numScrollbackLines;
2322             i < y + this.numScrollbackLines + h;
2323             i++) {
2324          this.copyLineSegment(x + incX, i + incY, x, i, w);
2325        }
2326      } else {
2327        // Scrolling down
2328        for (var i = y + this.numScrollbackLines + h;
2329             i-- > y + this.numScrollbackLines; ) {
2330          this.copyLineSegment(x + incX, i + incY, x, i, w);
2331        }
2332      }
2333
2334      // Clear blank regions
2335      if (incX > 0) {
2336        this.clearRegion(x, y, incX, h, color, style);
2337      } else if (incX < 0) {
2338        this.clearRegion(x + w + incX, y, -incX, h, color, style);
2339      }
2340      if (incY > 0) {
2341        this.clearRegion(x, y, w, incY, color, style);
2342      } else if (incY < 0) {
2343        this.clearRegion(x, y + h + incY, w, -incY, color, style);
2344      }
2345    }
2346
2347    // Reset scroll position
2348    this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2349                                this.cursorHeight + 1;
2350
2351    // Move cursor back to its original position
2352    hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2353  }
2354};
2355
2356VT100.prototype.copy = function(selection) {
2357  if (selection == undefined) {
2358    selection                = this.selection();
2359  }
2360  this.internalClipboard     = undefined;
2361  if (selection.length) {
2362    try {
2363      // IE
2364      this.cliphelper.value  = selection;
2365      this.cliphelper.select();
2366      this.cliphelper.createTextRange().execCommand('copy');
2367    } catch (e) {
2368      this.internalClipboard = selection;
2369    }
2370    this.cliphelper.value    = '';
2371  }
2372};
2373
2374VT100.prototype.copyLast = function() {
2375  // Opening the context menu can remove the selection. We try to prevent this
2376  // from happening, but that is not possible for all browsers. So, instead,
2377  // we compute the selection before showing the menu.
2378  this.copy(this.lastSelection);
2379};
2380
2381VT100.prototype.pasteFnc = function() {
2382  var clipboard     = undefined;
2383  if (this.internalClipboard != undefined) {
2384    clipboard       = this.internalClipboard;
2385  } else {
2386    try {
2387      this.cliphelper.value = '';
2388      this.cliphelper.createTextRange().execCommand('paste');
2389      clipboard     = this.cliphelper.value;
2390    } catch (e) {
2391    }
2392  }
2393  this.cliphelper.value = '';
2394  if (clipboard && this.menu.style.visibility == 'hidden') {
2395    return function() {
2396      this.keysPressed('' + clipboard);
2397    };
2398  } else {
2399    return undefined;
2400  }
2401};
2402
2403VT100.prototype.pasteBrowserFnc = function() {
2404  var clipboard     = prompt("Paste into this box:","");
2405  if (clipboard != undefined) {
2406       return this.keysPressed('' + clipboard);
2407  }
2408};
2409
2410VT100.prototype.toggleUTF = function() {
2411  this.utfEnabled   = !this.utfEnabled;
2412
2413  // We always persist the last value that the user selected. Not necessarily
2414  // the last value that a random program requested.
2415  this.utfPreferred = this.utfEnabled;
2416};
2417
2418VT100.prototype.toggleBell = function() {
2419  this.visualBell = !this.visualBell;
2420};
2421
2422VT100.prototype.toggleSoftKeyboard = function() {
2423  this.softKeyboard = !this.softKeyboard;
2424  this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2425};
2426
2427VT100.prototype.toggleDisableAlt = function() {
2428  this.disableAlt = !this.disableAlt;
2429};
2430
2431VT100.prototype.deselectKeys = function(elem) {
2432  if (elem && elem.className == 'selected') {
2433    elem.className = '';
2434  }
2435  for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2436    this.deselectKeys(elem);
2437  }
2438};
2439
2440VT100.prototype.showSoftKeyboard = function() {
2441  // Make sure no key is currently selected
2442  this.lastSelectedKey           = undefined;
2443  this.deselectKeys(this.keyboard);
2444  this.isShift                   = false;
2445  this.showShiftState(false);
2446  this.isCtrl                    = false;
2447  this.showCtrlState(false);
2448  this.isAlt                     = false;
2449  this.showAltState(false);
2450
2451  this.keyboard.style.left       = '0px';
2452  this.keyboard.style.top        = '0px';
2453  this.keyboard.style.width      = this.container.offsetWidth  + 'px';
2454  this.keyboard.style.height     = this.container.offsetHeight + 'px';
2455  this.keyboard.style.visibility = 'hidden';
2456  this.keyboard.style.display    = '';
2457
2458  var kbd                        = this.keyboard.firstChild;
2459  var scale                      = 1.0;
2460  var transform                  = this.getTransformName();
2461  if (transform) {
2462    kbd.style[transform]         = '';
2463    if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2464      scale                      = (kbd.offsetWidth/
2465                                    this.container.offsetWidth)/0.9;
2466    }
2467    if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2468      scale                      = Math.max((kbd.offsetHeight/
2469                                             this.container.offsetHeight)/0.9);
2470    }
2471    var style                    = this.getTransformStyle(transform,
2472                                              scale > 1.0 ? scale : undefined);
2473    kbd.style[transform]         = style;
2474  }
2475  if (transform == 'filter') {
2476    scale                        = 1.0;
2477  }
2478  kbd.style.left                 = ((this.container.offsetWidth -
2479                                     kbd.offsetWidth/scale)/2) + 'px';
2480  kbd.style.top                  = ((this.container.offsetHeight -
2481                                     kbd.offsetHeight/scale)/2) + 'px';
2482
2483  this.keyboard.style.visibility = 'visible';
2484};
2485
2486VT100.prototype.hideSoftKeyboard = function() {
2487  this.keyboard.style.display    = 'none';
2488};
2489
2490VT100.prototype.toggleCursorBlinking = function() {
2491  this.blinkingCursor = !this.blinkingCursor;
2492};
2493
2494VT100.prototype.about = function() {
2495  alert("VT100 Terminal Emulator " + VERSION +
2496        "\nCopyright 2008-2010 by Markus Gutschke\n" +
2497        "For more information check http://shellinabox.com");
2498};
2499
2500VT100.prototype.hideContextMenu = function() {
2501  this.menu.style.visibility = 'hidden';
2502  this.menu.style.top        = '-100px';
2503  this.menu.style.left       = '-100px';
2504  this.menu.style.width      = '0px';
2505  this.menu.style.height     = '0px';
2506};
2507
2508VT100.prototype.extendContextMenu = function(entries, actions) {
2509};
2510
2511VT100.prototype.showContextMenu = function(x, y) {
2512  this.menu.innerHTML         =
2513    '<table class="popup" ' +
2514           'cellpadding="0" cellspacing="0">' +
2515      '<tr><td>' +
2516        '<ul id="menuentries">' +
2517          '<li id="beginclipboard">Copy</li>' +
2518          '<li id="endclipboard">Paste</li>' +
2519          '<li id="browserclipboard">Paste from browser</li>' +
2520          '<hr />' +
2521          '<li id="reset">Reset</li>' +
2522          '<hr />' +
2523          '<li id="beginconfig">' +
2524             (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2525             'Unicode</li>' +
2526          '<li>' +
2527             (this.visualBell ? '<img src="enabled.gif" />' : '') +
2528             'Visual Bell</li>'+
2529          '<li>' +
2530             (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2531             'Onscreen Keyboard</li>' +
2532          '<li>' +
2533             (this.disableAlt ? '<img src="enabled.gif" />' : '') +
2534             'Disable Alt Key</li>' +
2535          '<li id="endconfig">' +
2536             (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2537             'Blinking Cursor</li>'+
2538          (this.usercss.firstChild ?
2539           '<hr id="beginusercss" />' +
2540           this.usercss.innerHTML +
2541           '<hr id="endusercss" />' :
2542           '<hr />') +
2543          '<li id="about">About...</li>' +
2544        '</ul>' +
2545      '</td></tr>' +
2546    '</table>';
2547
2548  var popup                   = this.menu.firstChild;
2549  var menuentries             = this.getChildById(popup, 'menuentries');
2550
2551  // Determine menu entries that should be disabled
2552  this.lastSelection          = this.selection();
2553  if (!this.lastSelection.length) {
2554    menuentries.firstChild.className
2555                              = 'disabled';
2556  }
2557  var p                       = this.pasteFnc();
2558  if (!p) {
2559    menuentries.childNodes[1].className
2560                              = 'disabled';
2561  }
2562
2563  // Actions for default items
2564  var actions                 = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
2565                                  this.toggleUTF, this.toggleBell,
2566                                  this.toggleSoftKeyboard,
2567                                  this.toggleDisableAlt,
2568                                  this.toggleCursorBlinking ];
2569
2570  // Actions for user CSS styles (if any)
2571  for (var i = 0; i < this.usercssActions.length; ++i) {
2572    actions[actions.length]   = this.usercssActions[i];
2573  }
2574  actions[actions.length]     = this.about;
2575
2576  // Allow subclasses to dynamically add entries to the context menu
2577  this.extendContextMenu(menuentries, actions);
2578
2579  // Hook up event listeners
2580  for (var node = menuentries.firstChild, i = 0; node;
2581       node = node.nextSibling) {
2582    if (node.tagName == 'LI') {
2583      if (node.className != 'disabled') {
2584        this.addListener(node, 'mouseover',
2585                         function(vt100, node) {
2586                           return function() {
2587                             node.className = 'hover';
2588                           }
2589                         }(this, node));
2590        this.addListener(node, 'mouseout',
2591                         function(vt100, node) {
2592                           return function() {
2593                             node.className = '';
2594                           }
2595                         }(this, node));
2596        this.addListener(node, 'mousedown',
2597                         function(vt100, action) {
2598                           return function(event) {
2599                             vt100.hideContextMenu();
2600                             action.call(vt100);
2601                             vt100.storeUserSettings();
2602                             return vt100.cancelEvent(event || window.event);
2603                           }
2604                         }(this, actions[i]));
2605        this.addListener(node, 'mouseup',
2606                         function(vt100) {
2607                           return function(event) {
2608                             return vt100.cancelEvent(event || window.event);
2609                           }
2610                         }(this));
2611        this.addListener(node, 'mouseclick',
2612                         function(vt100) {
2613                           return function(event) {
2614                             return vt100.cancelEvent(event || window.event);
2615                           }
2616                         }());
2617      }
2618      i++;
2619    }
2620  }
2621
2622  // Position menu next to the mouse pointer
2623  this.menu.style.left        = '0px';
2624  this.menu.style.top         = '0px';
2625  this.menu.style.width       =  this.container.offsetWidth  + 'px';
2626  this.menu.style.height      =  this.container.offsetHeight + 'px';
2627  popup.style.left            = '0px';
2628  popup.style.top             = '0px';
2629
2630  var margin                  = 2;
2631  if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2632    x              = this.container.offsetWidth-popup.clientWidth - margin - 1;
2633  }
2634  if (x < margin) {
2635    x                         = margin;
2636  }
2637  if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2638    y            = this.container.offsetHeight-popup.clientHeight - margin - 1;
2639  }
2640  if (y < margin) {
2641    y                         = margin;
2642  }
2643  popup.style.left            = x + 'px';
2644  popup.style.top             = y + 'px';
2645
2646  // Block all other interactions with the terminal emulator
2647  this.addListener(this.menu, 'click', function(vt100) {
2648                                         return function() {
2649                                           vt100.hideContextMenu();
2650                                         }
2651                                       }(this));
2652
2653  // Show the menu
2654  this.menu.style.visibility  = '';
2655};
2656
2657VT100.prototype.keysPressed = function(ch) {
2658  for (var i = 0; i < ch.length; i++) {
2659    var c = ch.charCodeAt(i);
2660    this.vt100(c >= 7 && c <= 15 ||
2661               c == 24 || c == 26 || c == 27 || c >= 32
2662               ? String.fromCharCode(c) : '<' + c + '>');
2663  }
2664};
2665
2666VT100.prototype.applyModifiers = function(ch, event) {
2667  if (ch) {
2668    if (event.ctrlKey) {
2669      if (ch >= 32 && ch <= 127) {
2670        // For historic reasons, some control characters are treated specially
2671        switch (ch) {
2672        case /* 3 */ 51: ch  =  27; break;
2673        case /* 4 */ 52: ch  =  28; break;
2674        case /* 5 */ 53: ch  =  29; break;
2675        case /* 6 */ 54: ch  =  30; break;
2676        case /* 7 */ 55: ch  =  31; break;
2677        case /* 8 */ 56: ch  = 127; break;
2678        case /* ? */ 63: ch  = 127; break;
2679        default:         ch &=  31; break;
2680        }
2681      }
2682    }
2683    return String.fromCharCode(ch);
2684  } else {
2685    return undefined;
2686  }
2687};
2688
2689VT100.prototype.handleKey = function(event) {
2690  // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2691  //            (event.shiftKey || event.ctrlKey || event.altKey ||
2692  //             event.metaKey ? ', ' +
2693  //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2694  //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2695  //            '\r\n');
2696  var ch, key;
2697  if (typeof event.charCode != 'undefined') {
2698    // non-IE keypress events have a translated charCode value. Also, our
2699    // fake events generated when receiving keydown events include this data
2700    // on all browsers.
2701    ch                                = event.charCode;
2702    key                               = event.keyCode;
2703  } else {
2704    // When sending a keypress event, IE includes the translated character
2705    // code in the keyCode field.
2706    ch                                = event.keyCode;
2707    key                               = undefined;
2708  }
2709
2710  // Apply modifier keys (ctrl and shift)
2711  if (ch) {
2712    key                               = undefined;
2713  }
2714  ch                                  = this.applyModifiers(ch, event);
2715
2716  // By this point, "ch" is either defined and contains the character code, or
2717  // it is undefined and "key" defines the code of a function key
2718  if (ch != undefined) {
2719    this.scrollable.scrollTop         = this.numScrollbackLines *
2720                                        this.cursorHeight + 1;
2721  } else {
2722    if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2723      // Many programs have difficulties dealing with parametrized escape
2724      // sequences for function keys. Thus, if ALT is the only modifier
2725      // key, return Emacs-style keycodes for commonly used keys.
2726      switch (key) {
2727      case  33: /* Page Up      */ ch = '\u001B<';                      break;
2728      case  34: /* Page Down    */ ch = '\u001B>';                      break;
2729      case  37: /* Left         */ ch = '\u001Bb';                      break;
2730      case  38: /* Up           */ ch = '\u001Bp';                      break;
2731      case  39: /* Right        */ ch = '\u001Bf';                      break;
2732      case  40: /* Down         */ ch = '\u001Bn';                      break;
2733      case  46: /* Delete       */ ch = '\u001Bd';                      break;
2734      default:                                                          break;
2735      }
2736    } else if (event.shiftKey && !event.ctrlKey &&
2737               !event.altKey && !event.metaKey) {
2738      switch (key) {
2739      case  33: /* Page Up      */ this.scrollBack();                   return;
2740      case  34: /* Page Down    */ this.scrollFore();                   return;
2741      default:                                                          break;
2742      }
2743    }
2744    if (ch == undefined) {
2745      switch (key) {
2746      case   8: /* Backspace    */ ch = '\u007f';                       break;
2747      case   9: /* Tab          */ ch = '\u0009';                       break;
2748      case  10: /* Return       */ ch = '\u000A';                       break;
2749      case  13: /* Enter        */ ch = this.crLfMode ?
2750                                        '\r\n' : '\r';                  break;
2751      case  16: /* Shift        */                                      return;
2752      case  17: /* Ctrl         */                                      return;
2753      case  18: /* Alt          */                                      return;
2754      case  19: /* Break        */                                      return;
2755      case  20: /* Caps Lock    */                                      return;
2756      case  27: /* Escape       */ ch = '\u001B';                       break;
2757      case  33: /* Page Up      */ ch = '\u001B[5~';                    break;
2758      case  34: /* Page Down    */ ch = '\u001B[6~';                    break;
2759      case  35: /* End          */ ch = '\u001BOF';                     break;
2760      case  36: /* Home         */ ch = '\u001BOH';                     break;
2761      case  37: /* Left         */ ch = this.cursorKeyMode ?
2762                             '\u001BOD' : '\u001B[D';                   break;
2763      case  38: /* Up           */ ch = this.cursorKeyMode ?
2764                             '\u001BOA' : '\u001B[A';                   break;
2765      case  39: /* Right        */ ch = this.cursorKeyMode ?
2766                             '\u001BOC' : '\u001B[C';                   break;
2767      case  40: /* Down         */ ch = this.cursorKeyMode ?
2768                             '\u001BOB' : '\u001B[B';                   break;
2769      case  45: /* Insert       */ ch = '\u001B[2~';                    break;
2770      case  46: /* Delete       */ ch = '\u001B[3~';                    break;
2771      case  91: /* Left Window  */                                      return;
2772      case  92: /* Right Window */                                      return;
2773      case  93: /* Select       */                                      return;
2774      case  96: /* 0            */ ch = this.applyModifiers(48, event); break;
2775      case  97: /* 1            */ ch = this.applyModifiers(49, event); break;
2776      case  98: /* 2            */ ch = this.applyModifiers(50, event); break;
2777      case  99: /* 3            */ ch = this.applyModifiers(51, event); break;
2778      case 100: /* 4            */ ch = this.applyModifiers(52, event); break;
2779      case 101: /* 5            */ ch = this.applyModifiers(53, event); break;
2780      case 102: /* 6            */ ch = this.applyModifiers(54, event); break;
2781      case 103: /* 7            */ ch = this.applyModifiers(55, event); break;
2782      case 104: /* 8            */ ch = this.applyModifiers(56, event); break;
2783      case 105: /* 9            */ ch = this.applyModifiers(58, event); break;
2784      case 106: /* *            */ ch = this.applyModifiers(42, event); break;
2785      case 107: /* +            */ ch = this.applyModifiers(43, event); break;
2786      case 109: /* -            */ ch = this.applyModifiers(45, event); break;
2787      case 110: /* .            */ ch = this.applyModifiers(46, event); break;
2788      case 111: /* /            */ ch = this.applyModifiers(47, event); break;
2789      case 112: /* F1           */ ch = '\u001BOP';                     break;
2790      case 113: /* F2           */ ch = '\u001BOQ';                     break;
2791      case 114: /* F3           */ ch = '\u001BOR';                     break;
2792      case 115: /* F4           */ ch = '\u001BOS';                     break;
2793      case 116: /* F5           */ ch = '\u001B[15~';                   break;
2794      case 117: /* F6           */ ch = '\u001B[17~';                   break;
2795      case 118: /* F7           */ ch = '\u001B[18~';                   break;
2796      case 119: /* F8           */ ch = '\u001B[19~';                   break;
2797      case 120: /* F9           */ ch = '\u001B[20~';                   break;
2798      case 121: /* F10          */ ch = '\u001B[21~';                   break;
2799      case 122: /* F11          */ ch = '\u001B[23~';                   break;
2800      case 123: /* F12          */ ch = '\u001B[24~';                   break;
2801      case 144: /* Num Lock     */                                      return;
2802      case 145: /* Scroll Lock  */                                      return;
2803      case 186: /* ;            */ ch = this.applyModifiers(59, event); break;
2804      // Conflicts with dead keys ` on Danish keyboard
2805      //                          ¸ on Slovenian keyboard
2806      // case 187: /* =            */ ch = this.applyModifiers(61, event); break;
2807      case 188: /* ,            */ ch = this.applyModifiers(44, event); break;
2808      case 189: /* -            */ ch = this.applyModifiers(45, event); break;
2809      case 190: /* .            */ ch = this.applyModifiers(46, event); break;
2810      case 191: /* /            */ ch = this.applyModifiers(47, event); break;
2811      // Conflicts with dead key " on Swiss keyboards
2812      // case 192: /* `            */ ch = this.applyModifiers(96, event); break;
2813      // Conflicts with dead key " on Swiss keyboards
2814      // case 219: /* [            */ ch = this.applyModifiers(91, event); break;
2815      case 220: /* \            */ ch = this.applyModifiers(92, event); break;
2816      // Conflicts with dead key ^ and ` on Swiss keaboards
2817      //                         ^ and " on French keyboards
2818      // case 221: /* ]            */ ch = this.applyModifiers(93, event); break;
2819      case 222: /* '            */ ch = this.applyModifiers(39, event); break;
2820      default:                                                          return;
2821      }
2822      this.scrollable.scrollTop       = this.numScrollbackLines *
2823                                        this.cursorHeight + 1;
2824    }
2825  }
2826
2827  // "ch" now contains the sequence of keycodes to send. But we might still
2828  // have to apply the effects of modifier keys.
2829  if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2830    var start, digit, part1, part2;
2831    if ((start = ch.substr(0, 2)) == '\u001B[') {
2832      for (part1 = start;
2833           part1.length < ch.length &&
2834             (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2835        part1                         = ch.substr(0, part1.length + 1);
2836      }
2837      part2                           = ch.substr(part1.length);
2838      if (part1.length > 2) {
2839        part1                        += ';';
2840      }
2841    } else if (start == '\u001BO') {
2842      part1                           = start;
2843      part2                           = ch.substr(2);
2844    }
2845    if (part1 != undefined) {
2846      ch                              = part1                                 +
2847                                       ((event.shiftKey             ? 1 : 0)  +
2848                                        (event.altKey|event.metaKey ? 2 : 0)  +
2849                                        (event.ctrlKey              ? 4 : 0) + 1) +
2850                                        part2;
2851    } else if (ch.length == 1 && (event.altKey || event.metaKey)
2852               && !this.disableAlt) {
2853      ch                              = '\u001B' + ch;
2854    }
2855  }
2856
2857  if (this.menu.style.visibility == 'hidden') {
2858    // this.vt100('R: c=');
2859    // for (var i = 0; i < ch.length; i++)
2860    //   this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2861    // this.vt100('\r\n');
2862    this.keysPressed(ch);
2863  }
2864};
2865
2866VT100.prototype.inspect = function(o, d) {
2867  if (d == undefined) {
2868    d       = 0;
2869  }
2870  var rc    = '';
2871  if (typeof o == 'object' && ++d < 2) {
2872    rc      = '[\r\n';
2873    for (i in o) {
2874      rc   += this.spaces(d * 2) + i + ' -> ';
2875      try {
2876        rc += this.inspect(o[i], d);
2877      } catch (e) {
2878        rc += '?' + '?' + '?\r\n';
2879      }
2880    }
2881    rc     += ']\r\n';
2882  } else {
2883    rc     += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2884  }
2885  return rc;
2886};
2887
2888VT100.prototype.checkComposedKeys = function(event) {
2889  // Composed keys (at least on Linux) do not generate normal events.
2890  // Instead, they get entered into the text field. We normally catch
2891  // this on the next keyup event.
2892  var s              = this.input.value;
2893  if (s.length) {
2894    this.input.value = '';
2895    if (this.menu.style.visibility == 'hidden') {
2896      this.keysPressed(s);
2897    }
2898  }
2899};
2900
2901VT100.prototype.fixEvent = function(event) {
2902  // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2903  // is used as a second-level selector, clear the modifier bits before
2904  // handling the event.
2905  if (event.ctrlKey && event.altKey) {
2906    var fake                = [ ];
2907    fake.charCode           = event.charCode;
2908    fake.keyCode            = event.keyCode;
2909    fake.ctrlKey            = false;
2910    fake.shiftKey           = event.shiftKey;
2911    fake.altKey             = false;
2912    fake.metaKey            = event.metaKey;
2913    return fake;
2914  }
2915
2916  // Some browsers fail to translate keys, if both shift and alt/meta is
2917  // pressed at the same time. We try to translate those cases, but that
2918  // only works for US keyboard layouts.
2919  var u                   = undefined;
2920  var s                   = undefined;
2921  if (event.shiftKey) {
2922    switch (this.lastNormalKeyDownEvent.keyCode) {
2923    case  39: /* ' -> " */ u = 39; s =  34; break;
2924    case  44: /* , -> < */ u = 44; s =  60; break;
2925    case  45: /* - -> _ */ u = 45; s =  95; break;
2926    case  46: /* . -> > */ u = 46; s =  62; break;
2927    case  47: /* / -> ? */ u = 47; s =  63; break;
2928
2929    case  48: /* 0 -> ) */ u = 48; s =  41; break;
2930    case  49: /* 1 -> ! */ u = 49; s =  33; break;
2931    case  50: /* 2 -> @ */ u = 50; s =  64; break;
2932    case  51: /* 3 -> # */ u = 51; s =  35; break;
2933    case  52: /* 4 -> $ */ u = 52; s =  36; break;
2934    case  53: /* 5 -> % */ u = 53; s =  37; break;
2935    case  54: /* 6 -> ^ */ u = 54; s =  94; break;
2936    case  55: /* 7 -> & */ u = 55; s =  38; break;
2937    case  56: /* 8 -> * */ u = 56; s =  42; break;
2938    case  57: /* 9 -> ( */ u = 57; s =  40; break;
2939    case  59: /* ; -> : */ u = 59; s =  58; break;
2940    case  61: /* = -> + */ u = 61; s =  43; break;
2941    case  91: /* [ -> { */ u = 91; s = 123; break;
2942    case  92: /* \ -> | */ u = 92; s = 124; break;
2943    case  93: /* ] -> } */ u = 93; s = 125; break;
2944    case  96: /* ` -> ~ */ u = 96; s = 126; break;
2945
2946    case 109: /* - -> _ */ u = 45; s =  95; break;
2947    case 111: /* / -> ? */ u = 47; s =  63; break;
2948
2949    case 186: /* ; -> : */ u = 59; s =  58; break;
2950    case 187: /* = -> + */ u = 61; s =  43; break;
2951    case 188: /* , -> < */ u = 44; s =  60; break;
2952    case 189: /* - -> _ */ u = 45; s =  95; break;
2953    case 190: /* . -> > */ u = 46; s =  62; break;
2954    case 191: /* / -> ? */ u = 47; s =  63; break;
2955    case 192: /* ` -> ~ */ u = 96; s = 126; break;
2956    case 219: /* [ -> { */ u = 91; s = 123; break;
2957    case 220: /* \ -> | */ u = 92; s = 124; break;
2958    case 221: /* ] -> } */ u = 93; s = 125; break;
2959    case 222: /* ' -> " */ u = 39; s =  34; break;
2960    default:                                break;
2961    }
2962  } else {
2963    var c = this.lastNormalKeyDownEvent.keyCode;
2964    if (c >= 65 && c <= 90) {
2965      u = c;
2966      s = u | 32;
2967    }
2968  }
2969  if (s && (event.charCode == u || event.charCode == 0)) {
2970    var fake              = [ ];
2971    fake.charCode         = s;
2972    fake.keyCode          = event.keyCode;
2973    fake.ctrlKey          = event.ctrlKey;
2974    fake.shiftKey         = event.shiftKey;
2975    fake.altKey           = event.altKey;
2976    fake.metaKey          = event.metaKey;
2977    return fake;
2978  }
2979  return event;
2980};
2981
2982VT100.prototype.keyDown = function(event) {
2983  // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2984  //            (event.shiftKey || event.ctrlKey || event.altKey ||
2985  //             event.metaKey ? ', ' +
2986  //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2987  //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2988  //            '\r\n');
2989  this.checkComposedKeys(event);
2990  this.lastKeyPressedEvent      = undefined;
2991  this.lastKeyDownEvent         = undefined;
2992  this.lastNormalKeyDownEvent   = event;
2993
2994  // Swiss keyboard conflicts:
2995  // [ 59
2996  // ] 192
2997  // ' 219 (dead key)
2998  // { 220
2999  // ~ 221 (dead key)
3000  // } 223
3001  // French keyoard conflicts:
3002  // ~ 50 (dead key)
3003  // } 107
3004  var asciiKey                  =
3005    event.keyCode ==  32                         ||
3006    event.keyCode >=  48 && event.keyCode <=  57 ||
3007    event.keyCode >=  65 && event.keyCode <=  90;
3008  var alphNumKey                =
3009    asciiKey                                     ||
3010    event.keyCode >=  58 && event.keyCode <=  64 ||
3011    event.keyCode >=  96 && event.keyCode <= 105 ||
3012    event.keyCode == 107 ||
3013    event.keyCode >= 160 && event.keyCode <= 192 ||
3014    event.keyCode >= 219 && event.keyCode <= 223 ||
3015    event.keyCode == 226;
3016  var normalKey                 =
3017    alphNumKey                                   ||
3018    event.keyCode == 106                         ||
3019    event.keyCode >= 109 && event.keyCode <= 111 ||
3020    event.keyCode == 229                         ||
3021    event.keyCode == 252;
3022  try {
3023    if (navigator.appName == 'Konqueror') {
3024      normalKey                |= event.keyCode < 128;
3025    }
3026  } catch (e) {
3027  }
3028
3029  if (this.disableAlt && normalKey) {
3030    return true;
3031  }
3032
3033  // We normally prefer to look at keypress events, as they perform the
3034  // translation from keyCode to charCode. This is important, as the
3035  // translation is locale-dependent.
3036  // But for some keys, we must intercept them during the keydown event,
3037  // as they would otherwise get interpreted by the browser.
3038  // Even, when doing all of this, there are some keys that we can never
3039  // intercept. This applies to some of the menu navigation keys in IE.
3040  // In fact, we see them, but we cannot stop IE from seeing them, too.
3041  if ((event.charCode || event.keyCode) &&
3042      ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
3043        !event.shiftKey &&
3044        // Some browsers signal AltGR as both CTRL and ALT. Do not try to
3045        // interpret this sequence ourselves, as some keyboard layouts use
3046        // it for second-level layouts.
3047        !(event.ctrlKey && event.altKey)) ||
3048       this.catchModifiersEarly && normalKey && !alphNumKey &&
3049       (event.ctrlKey || event.altKey || event.metaKey) ||
3050       !normalKey)) {
3051    this.lastKeyDownEvent       = event;
3052    var fake                    = [ ];
3053    fake.ctrlKey                = event.ctrlKey;
3054    fake.shiftKey               = event.shiftKey;
3055    fake.altKey                 = event.altKey;
3056    fake.metaKey                = event.metaKey;
3057    if (asciiKey) {
3058      fake.charCode             = event.keyCode;
3059      fake.keyCode              = 0;
3060    } else {
3061      fake.charCode             = 0;
3062      fake.keyCode              = event.keyCode;
3063    }
3064    fake                        = this.fixEvent(fake);
3065
3066    this.handleKey(fake);
3067    this.lastNormalKeyDownEvent = undefined;
3068
3069    try {
3070      // For non-IE browsers
3071      event.stopPropagation();
3072      event.preventDefault();
3073    } catch (e) {
3074    }
3075    try {
3076      // For IE
3077      event.cancelBubble = true;
3078      event.returnValue  = false;
3079      event.keyCode      = 0;
3080    } catch (e) {
3081    }
3082
3083    return false;
3084  }
3085  return true;
3086};
3087
3088VT100.prototype.keyPressed = function(event) {
3089  // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
3090  //            (event.shiftKey || event.ctrlKey || event.altKey ||
3091  //             event.metaKey ? ', ' +
3092  //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3093  //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3094  //            '\r\n');
3095  if (this.lastKeyDownEvent) {
3096    // If we already processed the key on keydown, do not process it
3097    // again here. Ideally, the browser should not even have generated a
3098    // keypress event in this case. But that does not appear to always work.
3099    this.lastKeyDownEvent     = undefined;
3100  } else {
3101    this.handleKey(event.altKey || event.metaKey
3102                   ? this.fixEvent(event) : event);
3103  }
3104
3105  try {
3106    // For non-IE browsers
3107    event.preventDefault();
3108  } catch (e) {
3109  }
3110
3111  try {
3112    // For IE
3113    event.cancelBubble = true;
3114    event.returnValue  = false;
3115    event.keyCode      = 0;
3116  } catch (e) {
3117  }
3118
3119  this.lastNormalKeyDownEvent = undefined;
3120  this.lastKeyPressedEvent    = event;
3121  return false;
3122};
3123
3124VT100.prototype.keyUp = function(event) {
3125  // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3126  //            (event.shiftKey || event.ctrlKey || event.altKey ||
3127  //             event.metaKey ? ', ' +
3128  //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3129  //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3130  //            '\r\n');
3131  if (this.lastKeyPressedEvent) {
3132    // The compose key on Linux occasionally confuses the browser and keeps
3133    // inserting bogus characters into the input field, even if just a regular
3134    // key has been pressed. Detect this case and drop the bogus characters.
3135    (event.target ||
3136     event.srcElement).value      = '';
3137  } else {
3138    // This is usually were we notice that a key has been composed and
3139    // thus failed to generate normal events.
3140    this.checkComposedKeys(event);
3141
3142    // Some browsers don't report keypress events if ctrl or alt is pressed
3143    // for non-alphanumerical keys. Patch things up for now, but in the
3144    // future we will catch these keys earlier (in the keydown handler).
3145    if (this.lastNormalKeyDownEvent) {
3146      // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3147      this.catchModifiersEarly    = true;
3148      var asciiKey                =
3149        event.keyCode ==  32                         ||
3150        // Conflicts with dead key ~ (code 50) on French keyboards
3151        //event.keyCode >=  48 && event.keyCode <=  57 ||
3152        event.keyCode >=  48 && event.keyCode <=  49 ||
3153        event.keyCode >=  51 && event.keyCode <=  57 ||
3154        event.keyCode >=  65 && event.keyCode <=  90;
3155      var alphNumKey              =
3156        asciiKey                                     ||
3157        event.keyCode ==  50                         ||
3158        event.keyCode >=  96 && event.keyCode <= 105;
3159      // Not used ???
3160      var normalKey               =
3161        alphNumKey                                   ||
3162        event.keyCode ==  59 || event.keyCode ==  61 ||
3163        event.keyCode == 106 || event.keyCode == 107 ||
3164        event.keyCode >= 109 && event.keyCode <= 111 ||
3165        event.keyCode >= 186 && event.keyCode <= 192 ||
3166        event.keyCode >= 219 && event.keyCode <= 223 ||
3167        event.keyCode == 252;
3168      var fake                    = [ ];
3169      fake.ctrlKey                = event.ctrlKey;
3170      fake.shiftKey               = event.shiftKey;
3171      fake.altKey                 = event.altKey;
3172      fake.metaKey                = event.metaKey;
3173      if (asciiKey) {
3174        fake.charCode             = event.keyCode;
3175        fake.keyCode              = 0;
3176      } else {
3177        fake.charCode             = 0;
3178        fake.keyCode              = event.keyCode;
3179      }
3180      if (event.ctrlKey || event.altKey || event.metaKey) {
3181        fake                      = this.fixEvent(fake);
3182      }
3183      this.lastNormalKeyDownEvent = undefined;
3184      this.handleKey(fake);
3185    }
3186  }
3187
3188  try {
3189    // For IE
3190    event.cancelBubble            = true;
3191    event.returnValue             = false;
3192    event.keyCode                 = 0;
3193  } catch (e) {
3194  }
3195
3196  this.lastKeyDownEvent           = undefined;
3197  this.lastKeyPressedEvent        = undefined;
3198  return false;
3199};
3200
3201VT100.prototype.animateCursor = function(inactive) {
3202  if (!this.cursorInterval) {
3203    this.cursorInterval       = setInterval(
3204      function(vt100) {
3205        return function() {
3206          vt100.animateCursor();
3207
3208          // Use this opportunity to check whether the user entered a composed
3209          // key, or whether somebody pasted text into the textfield.
3210          vt100.checkComposedKeys();
3211        }
3212      }(this), 500);
3213  }
3214  if (inactive != undefined || this.cursor.className != 'inactive') {
3215    if (inactive) {
3216      this.cursor.className   = 'inactive';
3217    } else {
3218      if (this.blinkingCursor) {
3219        this.cursor.className = this.cursor.className == 'bright'
3220                                ? 'dim' : 'bright';
3221      } else {
3222        this.cursor.className = 'bright';
3223      }
3224    }
3225  }
3226};
3227
3228VT100.prototype.blurCursor = function() {
3229  this.animateCursor(true);
3230};
3231
3232VT100.prototype.focusCursor = function() {
3233  this.animateCursor(false);
3234};
3235
3236VT100.prototype.flashScreen = function() {
3237  this.isInverted       = !this.isInverted;
3238  this.refreshInvertedState();
3239  this.isInverted       = !this.isInverted;
3240  setTimeout(function(vt100) {
3241               return function() {
3242                 vt100.refreshInvertedState();
3243               };
3244             }(this), 100);
3245};
3246
3247VT100.prototype.beep = function() {
3248  if (this.visualBell) {
3249    this.flashScreen();
3250  } else {
3251    try {
3252      this.beeper.Play();
3253    } catch (e) {
3254      try {
3255        this.beeper.src = 'beep.wav';
3256      } catch (e) {
3257      }
3258    }
3259  }
3260};
3261
3262VT100.prototype.bs = function() {
3263  if (this.cursorX > 0) {
3264    this.gotoXY(this.cursorX - 1, this.cursorY);
3265    this.needWrap = false;
3266  }
3267};
3268
3269VT100.prototype.ht = function(count) {
3270  if (count == undefined) {
3271    count        = 1;
3272  }
3273  var cx         = this.cursorX;
3274  while (count-- > 0) {
3275    while (cx++ < this.terminalWidth) {
3276      var tabState = this.userTabStop[cx];
3277      if (tabState == false) {
3278        // Explicitly cleared tab stop
3279        continue;
3280      } else if (tabState) {
3281        // Explicitly set tab stop
3282        break;
3283      } else {
3284        // Default tab stop at each eighth column
3285        if (cx % 8 == 0) {
3286          break;
3287        }
3288      }
3289    }
3290  }
3291  if (cx > this.terminalWidth - 1) {
3292    cx           = this.terminalWidth - 1;
3293  }
3294  if (cx != this.cursorX) {
3295    this.gotoXY(cx, this.cursorY);
3296  }
3297};
3298
3299VT100.prototype.rt = function(count) {
3300  if (count == undefined) {
3301    count          = 1 ;
3302  }
3303  var cx           = this.cursorX;
3304  while (count-- > 0) {
3305    while (cx-- > 0) {
3306      var tabState = this.userTabStop[cx];
3307      if (tabState == false) {
3308        // Explicitly cleared tab stop
3309        continue;
3310      } else if (tabState) {
3311        // Explicitly set tab stop
3312        break;
3313      } else {
3314        // Default tab stop at each eighth column
3315        if (cx % 8 == 0) {
3316          break;
3317        }
3318      }
3319    }
3320  }
3321  if (cx < 0) {
3322    cx             = 0;
3323  }
3324  if (cx != this.cursorX) {
3325    this.gotoXY(cx, this.cursorY);
3326  }
3327};
3328
3329VT100.prototype.cr = function() {
3330  this.gotoXY(0, this.cursorY);
3331  this.needWrap = false;
3332};
3333
3334VT100.prototype.lf = function(count) {
3335  if (count == undefined) {
3336    count    = 1;
3337  } else {
3338    if (count > this.terminalHeight) {
3339      count  = this.terminalHeight;
3340    }
3341    if (count < 1) {
3342      count  = 1;
3343    }
3344  }
3345  while (count-- > 0) {
3346    if (this.cursorY == this.bottom - 1) {
3347      this.scrollRegion(0, this.top + 1,
3348                        this.terminalWidth, this.bottom - this.top - 1,
3349                        0, -1, this.color, this.style);
3350    } else if (this.cursorY < this.terminalHeight - 1) {
3351      this.gotoXY(this.cursorX, this.cursorY + 1);
3352    }
3353  }
3354};
3355
3356VT100.prototype.ri = function(count) {
3357  if (count == undefined) {
3358    count   = 1;
3359  } else {
3360    if (count > this.terminalHeight) {
3361      count = this.terminalHeight;
3362    }
3363    if (count < 1) {
3364      count = 1;
3365    }
3366  }
3367  while (count-- > 0) {
3368    if (this.cursorY == this.top) {
3369      this.scrollRegion(0, this.top,
3370                        this.terminalWidth, this.bottom - this.top - 1,
3371                        0, 1, this.color, this.style);
3372    } else if (this.cursorY > 0) {
3373      this.gotoXY(this.cursorX, this.cursorY - 1);
3374    }
3375  }
3376  this.needWrap = false;
3377};
3378
3379VT100.prototype.respondID = function() {
3380  this.respondString += '\u001B[?6c';
3381};
3382
3383VT100.prototype.respondSecondaryDA = function() {
3384  this.respondString += '\u001B[>0;0;0c';
3385};
3386
3387
3388VT100.prototype.updateStyle = function() {
3389  var fg          = '';
3390  var bg          = '';
3391  this.style      = '';
3392
3393  if (this.attr & ATTR_UNDERLINE) {
3394    this.style   += 'text-decoration: underline;';
3395  }
3396  if (this.attr & ATTR_BLINK) {
3397    this.style   += 'text-decoration: blink;';
3398  }
3399
3400  // Forground color
3401  if (this.attrFg) {
3402    // 256 color mode
3403    fg            = this.attrFg
3404  } else if (this.attr & ATTR_DEF_FG) {
3405    fg            = 'Def';
3406  } else {
3407    fg            = this.attr & 0xF;
3408    if (this.attr & ATTR_BRIGHT) {
3409      fg         |= 8;
3410      this.style += 'font-weight: bold;';
3411    }
3412  }
3413
3414  // Background color
3415  if (this.attrBg) {
3416    // 256 color mode
3417    bg            = this.attrBg
3418  } else if (this.attr & ATTR_DEF_BG) {
3419    bg            = 'Def';
3420  } else {
3421    bg            = (this.attr >> 4) & 0xF;
3422  }
3423
3424  // Reverse colors
3425  if (this.attr & ATTR_REVERSE) {
3426    var tmpFg     = fg;
3427    var tmpBg     = bg;
3428    fg            = (tmpBg == 'Def') ? 'DefR' : tmpBg;
3429    bg            = (tmpFg == 'Def') ? 'DefR' : tmpFg;
3430  }
3431
3432  this.color      = 'ansi' + fg + ' bgAnsi' + bg;
3433};
3434
3435VT100.prototype.setAttrColors = function(attr) {
3436  if (attr != this.attr) {
3437    this.attr = attr;
3438    this.updateStyle();
3439  }
3440};
3441
3442VT100.prototype.saveCursor = function() {
3443  this.savedX[this.currentScreen]      = this.cursorX;
3444  this.savedY[this.currentScreen]      = this.cursorY;
3445  this.savedAttr[this.currentScreen]   = this.attr;
3446  this.savedAttrFg[this.currentScreen] = this.attrFg;
3447  this.savedAttrBg[this.currentScreen] = this.attrBg;
3448  this.savedUseGMap                    = this.useGMap;
3449  for (var i = 0; i < 4; i++) {
3450    this.savedGMap[i]                  = this.GMap[i];
3451  }
3452  this.savedValid[this.currentScreen]  = true;
3453};
3454
3455VT100.prototype.restoreCursor = function() {
3456  if (!this.savedValid[this.currentScreen]) {
3457    return;
3458  }
3459  this.attr      = this.savedAttr[this.currentScreen];
3460  this.attrFg    = this.savedAttrFg[this.currentScreen];
3461  this.attrBg    = this.savedAttrBg[this.currentScreen];
3462  this.updateStyle();
3463  this.useGMap   = this.savedUseGMap;
3464  for (var i = 0; i < 4; i++) {
3465    this.GMap[i] = this.savedGMap[i];
3466  }
3467  this.translate = this.GMap[this.useGMap];
3468  this.needWrap  = false;
3469  this.gotoXY(this.savedX[this.currentScreen],
3470              this.savedY[this.currentScreen]);
3471};
3472
3473VT100.prototype.getTransformName = function() {
3474  var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3475  for (var i = 0; i < styles.length; ++i) {
3476    if (typeof this.console[0].style[styles[i]] != 'undefined') {
3477      return styles[i];
3478    }
3479  }
3480  return undefined;
3481};
3482
3483VT100.prototype.getTransformStyle = function(transform, scale) {
3484  return scale && scale != 1.0
3485    ? transform == 'filter'
3486      ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3487                                 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3488                                 "sizingMethod='auto expand')"
3489      : 'translateX(-50%) ' +
3490        'scaleX(' + (1.0/scale) + ') ' +
3491        'translateX(50%)'
3492    : '';
3493};
3494
3495VT100.prototype.set80_132Mode = function(state) {
3496  var transform                  = this.getTransformName();
3497  if (transform) {
3498    if ((this.console[this.currentScreen].style[transform] != '') == state) {
3499      return;
3500    }
3501    var style                    = state ?
3502                                   this.getTransformStyle(transform, 1.65):'';
3503    this.console[this.currentScreen].style[transform] = style;
3504    this.cursor.style[transform] = style;
3505    this.space.style[transform]  = style;
3506    this.scale                   = state ? 1.65 : 1.0;
3507    if (transform == 'filter') {
3508      this.console[this.currentScreen].style.width = state ? '165%' : '';
3509    }
3510    this.resizer();
3511  }
3512};
3513
3514VT100.prototype.setMode = function(state) {
3515  for (var i = 0; i <= this.npar; i++) {
3516    if (this.isQuestionMark) {
3517      switch (this.par[i]) {
3518      case  1: this.cursorKeyMode      = state;                      break;
3519      case  3: this.set80_132Mode(state);                            break;
3520      case  5: this.isInverted = state; this.refreshInvertedState(); break;
3521      case  6: this.offsetMode         = state;                      break;
3522      case  7: this.autoWrapMode       = state;                      break;
3523      case 1000:
3524      case  9: this.mouseReporting     = state;                      break;
3525      case 25: this.cursorNeedsShowing = state;
3526               if (state) { this.showCursor(); }
3527               else       { this.hideCursor(); }                     break;
3528      case 1047:
3529      case 1049:
3530      case 47: this.enableAlternateScreen(state);                    break;
3531      default:                                                       break;
3532      }
3533    } else {
3534      switch (this.par[i]) {
3535      case  3: this.dispCtrl           = state;                      break;
3536      case  4: this.insertMode         = state;                      break;
3537      case  20:this.crLfMode           = state;                      break;
3538      default:                                                       break;
3539      }
3540    }
3541  }
3542};
3543
3544VT100.prototype.statusReport = function() {
3545  // Ready and operational.
3546  this.respondString += '\u001B[0n';
3547};
3548
3549VT100.prototype.cursorReport = function() {
3550  this.respondString += '\u001B[' +
3551                        (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3552                        ';' +
3553                        (this.cursorX + 1) +
3554                        'R';
3555};
3556
3557VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3558  // Changing of cursor color is not implemented.
3559};
3560
3561VT100.prototype.openPrinterWindow = function() {
3562  var rc            = true;
3563  try {
3564    if (!this.printWin || this.printWin.closed) {
3565      this.printWin = window.open('', 'print-output',
3566        'width=800,height=600,directories=no,location=no,menubar=yes,' +
3567        'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3568      this.printWin.document.body.innerHTML =
3569        '<link rel="stylesheet" href="' +
3570          document.location.protocol + '//' + document.location.host +
3571          document.location.pathname.replace(/[^/]*$/, '') +
3572          'print-styles.css" type="text/css">\n' +
3573        '<div id="options"><input id="autoprint" type="checkbox"' +
3574          (this.autoprint ? ' checked' : '') + '>' +
3575          'Automatically, print page(s) when job is ready' +
3576        '</input></div>\n' +
3577        '<div id="spacer"><input type="checkbox">&nbsp;</input></div>' +
3578        '<pre id="print"></pre>\n';
3579      var autoprint = this.printWin.document.getElementById('autoprint');
3580      this.addListener(autoprint, 'click',
3581                       (function(vt100, autoprint) {
3582                         return function() {
3583                           vt100.autoprint = autoprint.checked;
3584                           vt100.storeUserSettings();
3585                           return false;
3586                         };
3587                       })(this, autoprint));
3588      this.printWin.document.title = 'ShellInABox Printer Output';
3589    }
3590  } catch (e) {
3591    // Maybe, a popup blocker prevented us from working. Better catch the
3592    // exception, so that we won't break the entire terminal session. The
3593    // user probably needs to disable the blocker first before retrying the
3594    // operation.
3595    rc              = false;
3596  }
3597  rc               &= this.printWin && !this.printWin.closed &&
3598                      (this.printWin.innerWidth ||
3599                       this.printWin.document.documentElement.clientWidth ||
3600                       this.printWin.document.body.clientWidth) > 1;
3601
3602  if (!rc && this.printing == 100) {
3603    // Different popup blockers work differently. We try to detect a couple
3604    // of common methods. And then we retry again a brief amount later, as
3605    // false positives are otherwise possible. If we are sure that there is
3606    // a popup blocker in effect, we alert the user to it. This is helpful
3607    // as some popup blockers have minimal or no UI, and the user might not
3608    // notice that they are missing the popup. In any case, we only show at
3609    // most one message per print job.
3610    this.printing   = true;
3611    setTimeout((function(win) {
3612                  return function() {
3613                    if (!win || win.closed ||
3614                        (win.innerWidth ||
3615                         win.document.documentElement.clientWidth ||
3616                         win.document.body.clientWidth) <= 1) {
3617                      alert('Attempted to print, but a popup blocker ' +
3618                            'prevented the printer window from opening');
3619                    }
3620                  };
3621                })(this.printWin), 2000);
3622  }
3623  return rc;
3624};
3625
3626VT100.prototype.sendToPrinter = function(s) {
3627  this.openPrinterWindow();
3628  try {
3629    var doc   = this.printWin.document;
3630    var print = doc.getElementById('print');
3631    if (print.lastChild && print.lastChild.nodeName == '#text') {
3632      print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3633    } else {
3634      print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3635    }
3636  } catch (e) {
3637    // There probably was a more aggressive popup blocker that prevented us
3638    // from accessing the printer windows.
3639  }
3640};
3641
3642VT100.prototype.sendControlToPrinter = function(ch) {
3643  // We get called whenever doControl() is active. But for the printer, we
3644  // only implement a basic line printer that doesn't understand most of
3645  // the escape sequences of the VT100 terminal. In fact, the only escape
3646  // sequence that we really need to recognize is '^[[5i' for turning the
3647  // printer off.
3648  try {
3649    switch (ch) {
3650    case  9:
3651      // HT
3652      this.openPrinterWindow();
3653      var doc                 = this.printWin.document;
3654      var print               = doc.getElementById('print');
3655      var chars               = print.lastChild &&
3656                                print.lastChild.nodeName == '#text' ?
3657                                print.lastChild.textContent.length : 0;
3658      this.sendToPrinter(this.spaces(8 - (chars % 8)));
3659      break;
3660    case 10:
3661      // CR
3662      break;
3663    case 12:
3664      // FF
3665      this.openPrinterWindow();
3666      var pageBreak           = this.printWin.document.createElement('div');
3667      pageBreak.className     = 'pagebreak';
3668      pageBreak.innerHTML     = '<hr />';
3669      this.printWin.document.getElementById('print').appendChild(pageBreak);
3670      break;
3671    case 13:
3672      // LF
3673      this.openPrinterWindow();
3674      var lineBreak           = this.printWin.document.createElement('br');
3675      this.printWin.document.getElementById('print').appendChild(lineBreak);
3676      break;
3677    case 27:
3678      // ESC
3679      this.isEsc              = ESesc;
3680      break;
3681    default:
3682      switch (this.isEsc) {
3683      case ESesc:
3684        this.isEsc            = ESnormal;
3685        switch (ch) {
3686        case 0x5B /*[*/:
3687          this.isEsc          = ESsquare;
3688          break;
3689        default:
3690          break;
3691        }
3692        break;
3693      case ESsquare:
3694        this.npar             = 0;
3695        this.par              = [ 0, 0, 0, 0, 0, 0, 0, 0,
3696                                  0, 0, 0, 0, 0, 0, 0, 0 ];
3697        this.isEsc            = ESgetpars;
3698        this.isQuestionMark   = ch == 0x3F /*?*/;
3699        if (this.isQuestionMark) {
3700          break;
3701        }
3702        // Fall through
3703      case ESgetpars:
3704        if (ch == 0x3B /*;*/) {
3705          this.npar++;
3706          break;
3707        } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3708          var par             = this.par[this.npar];
3709          if (par == undefined) {
3710            par               = 0;
3711          }
3712          this.par[this.npar] = 10*par + (ch & 0xF);
3713          break;
3714        } else {
3715          this.isEsc          = ESgotpars;
3716        }
3717        // Fall through
3718      case ESgotpars:
3719        this.isEsc            = ESnormal;
3720        if (this.isQuestionMark) {
3721          break;
3722        }
3723        switch (ch) {
3724        case 0x69 /*i*/:
3725          this.csii(this.par[0]);
3726          break;
3727        default:
3728          break;
3729        }
3730        break;
3731      default:
3732        this.isEsc            = ESnormal;
3733        break;
3734      }
3735      break;
3736    }
3737  } catch (e) {
3738    // There probably was a more aggressive popup blocker that prevented us
3739    // from accessing the printer windows.
3740  }
3741};
3742
3743VT100.prototype.csiAt = function(number) {
3744  // Insert spaces
3745  if (number == 0) {
3746    number      = 1;
3747  }
3748  if (number > this.terminalWidth - this.cursorX) {
3749    number      = this.terminalWidth - this.cursorX;
3750  }
3751  this.scrollRegion(this.cursorX, this.cursorY,
3752                    this.terminalWidth - this.cursorX - number, 1,
3753                    number, 0, this.color, this.style);
3754  this.needWrap = false;
3755};
3756
3757VT100.prototype.csii = function(number) {
3758  // Printer control
3759  switch (number) {
3760  case 0: // Print Screen
3761    window.print();
3762    break;
3763  case 4: // Stop printing
3764    try {
3765      if (this.printing && this.printWin && !this.printWin.closed) {
3766        var print = this.printWin.document.getElementById('print');
3767        while (print.lastChild &&
3768               print.lastChild.tagName == 'DIV' &&
3769               print.lastChild.className == 'pagebreak') {
3770          // Remove trailing blank pages
3771          print.removeChild(print.lastChild);
3772        }
3773        if (this.autoprint) {
3774          this.printWin.print();
3775        }
3776      }
3777    } catch (e) {
3778    }
3779    this.printing = false;
3780    break;
3781  case 5: // Start printing
3782    if (!this.printing && this.printWin && !this.printWin.closed) {
3783      this.printWin.document.getElementById('print').innerHTML = '';
3784    }
3785    this.printing = 100;
3786    break;
3787  default:
3788    break;
3789  }
3790};
3791
3792VT100.prototype.csiJ = function(number) {
3793  switch (number) {
3794  case 0: // Erase from cursor to end of display
3795    this.clearRegion(this.cursorX, this.cursorY,
3796                     this.terminalWidth - this.cursorX, 1,
3797                     this.color, this.style);
3798    if (this.cursorY < this.terminalHeight-2) {
3799      this.clearRegion(0, this.cursorY+1,
3800                       this.terminalWidth, this.terminalHeight-this.cursorY-1,
3801                       this.color, this.style);
3802    }
3803    break;
3804  case 1: // Erase from start to cursor
3805    if (this.cursorY > 0) {
3806      this.clearRegion(0, 0,
3807                       this.terminalWidth, this.cursorY,
3808                       this.color, this.style);
3809    }
3810    this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3811                     this.color, this.style);
3812    break;
3813  case 2: // Erase whole display
3814    this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3815                     this.color, this.style);
3816    break;
3817  default:
3818    return;
3819  }
3820  this.needWrap = false;
3821};
3822
3823VT100.prototype.csiK = function(number) {
3824  switch (number) {
3825  case 0: // Erase from cursor to end of line
3826    this.clearRegion(this.cursorX, this.cursorY,
3827                     this.terminalWidth - this.cursorX, 1,
3828                     this.color, this.style);
3829    break;
3830  case 1: // Erase from start of line to cursor
3831    this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3832                     this.color, this.style);
3833    break;
3834  case 2: // Erase whole line
3835    this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3836                     this.color, this.style);
3837    break;
3838  default:
3839    return;
3840  }
3841  this.needWrap = false;
3842};
3843
3844VT100.prototype.csiL = function(number) {
3845  // Open line by inserting blank line(s)
3846  if (this.cursorY >= this.bottom) {
3847    return;
3848  }
3849  if (number == 0) {
3850    number = 1;
3851  }
3852  if (number > this.bottom - this.cursorY) {
3853    number = this.bottom - this.cursorY;
3854  }
3855  this.scrollRegion(0, this.cursorY,
3856                    this.terminalWidth, this.bottom - this.cursorY - number,
3857                    0, number, this.color, this.style);
3858  this.needWrap = false;
3859};
3860
3861VT100.prototype.csiM = function(number) {
3862  // Delete line(s), scrolling up the bottom of the screen.
3863  if (this.cursorY >= this.bottom) {
3864    return;
3865  }
3866  if (number == 0) {
3867    number = 1;
3868  }
3869  if (number > this.bottom - this.cursorY) {
3870    number = this.bottom - this.cursorY;
3871  }
3872  this.scrollRegion(0, this.cursorY + number,
3873                    this.terminalWidth, this.bottom - this.cursorY - number,
3874                    0, -number, this.color, this.style);
3875  this.needWrap = false;
3876};
3877
3878VT100.prototype.csim = function() {
3879  for (var i = 0; i <= this.npar; i++) {
3880    switch (this.par[i]) {
3881    case 0:
3882      this.attr         = ATTR_DEFAULT;
3883      this.attrFg       = false;
3884      this.attrBg       = false;
3885      break;
3886    case 1:  this.attr  = (this.attr & ~ATTR_DIM)|ATTR_BRIGHT;         break;
3887    case 2:  this.attr  = (this.attr & ~ATTR_BRIGHT)|ATTR_DIM;         break;
3888    case 4:  this.attr |= ATTR_UNDERLINE;                              break;
3889    case 5:  this.attr |= ATTR_BLINK;                                  break;
3890    case 7:  this.attr |= ATTR_REVERSE;                                break;
3891    case 10:
3892      this.translate    = this.GMap[this.useGMap];
3893      this.dispCtrl     = false;
3894      this.toggleMeta   = false;
3895      break;
3896    case 11:
3897      this.translate    = this.CodePage437Map;
3898      this.dispCtrl     = true;
3899      this.toggleMeta   = false;
3900      break;
3901    case 12:
3902      this.translate    = this.CodePage437Map;
3903      this.dispCtrl     = true;
3904      this.toggleMeta   = true;
3905      break;
3906    case 21:
3907    case 22: this.attr &= ~(ATTR_BRIGHT|ATTR_DIM);                     break;
3908    case 24: this.attr &= ~ ATTR_UNDERLINE;                            break;
3909    case 25: this.attr &= ~ ATTR_BLINK;                                break;
3910    case 27: this.attr &= ~ ATTR_REVERSE;                              break;
3911    case 38:
3912      if (this.npar >= (i+2) && this.par[i+1] == 5) {
3913        // Foreground color for extended color mode (256 colors). Escape code is formatted
3914        // as: ESC 38; 5; 0-255. Last parameter is color code in range [0-255]. This is
3915        // not VT100 standard.
3916        this.attrFg     = (this.par[i+2] >= 0 && this.par[i+2] <= 255) ? this.par[i+2] : false;
3917        i              += 2;
3918      } else {
3919        // Default VT100 behaviour.
3920        this.attr       = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|0x0F))|ATTR_UNDERLINE | ATTR_DEF_FG;
3921      }
3922      break;
3923    case 39:
3924      this.attr         = (this.attr & ~(ATTR_DIM|ATTR_BRIGHT|ATTR_UNDERLINE|0x0F)) | ATTR_DEF_FG;
3925      this.attrFg       = false;
3926      break;
3927    case 48:
3928      if (this.npar >= (i+2) && this.par[i+1] == 5) {
3929        // Background color for extended color mode (256 colors). Escape code is formatted
3930        // as: ESC 48; 5; 0-255. Last parameter is color code in range [0-255]. This is
3931        // not VT100 standard.
3932        this.attrBg     = (this.par[i+2] >= 0 && this.par[i+2] <= 255) ? this.par[i+2] : false;
3933        i              += 2;
3934      }
3935      break;
3936    case 49:
3937      this.attr        |= (0xF0|ATTR_DEF_BG);
3938      this.attrBg       = false;
3939      break;
3940    default:
3941      if (this.par[i] >= 30 && this.par[i] <= 37) {
3942          var fg        = this.par[i] - 30;
3943          this.attr     = ((this.attr & ~0x0F) | fg) & ~(ATTR_DEF_FG);
3944          this.attrFg   = false;
3945      } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3946          var bg        = this.par[i] - 40;
3947          this.attr     = ((this.attr & ~0xF0) | (bg << 4)) & ~(ATTR_DEF_BG);
3948          this.attrBg   = false;
3949      }
3950      break;
3951    }
3952  }
3953  this.updateStyle();
3954};
3955
3956VT100.prototype.csiP = function(number) {
3957  // Delete character(s) following cursor
3958  if (number == 0) {
3959    number = 1;
3960  }
3961  if (number > this.terminalWidth - this.cursorX) {
3962    number = this.terminalWidth - this.cursorX;
3963  }
3964  this.scrollRegion(this.cursorX + number, this.cursorY,
3965                    this.terminalWidth - this.cursorX - number, 1,
3966                    -number, 0, this.color, this.style);
3967  this.needWrap = false;
3968};
3969
3970VT100.prototype.csiX = function(number) {
3971  // Clear characters following cursor
3972  if (number == 0) {
3973    number++;
3974  }
3975  if (number > this.terminalWidth - this.cursorX) {
3976    number = this.terminalWidth - this.cursorX;
3977  }
3978  this.clearRegion(this.cursorX, this.cursorY, number, 1,
3979                   this.color, this.style);
3980  this.needWrap = false;
3981};
3982
3983VT100.prototype.settermCommand = function() {
3984  // Setterm commands are not implemented
3985};
3986
3987VT100.prototype.doControl = function(ch) {
3988  if (this.printing) {
3989    this.sendControlToPrinter(ch);
3990    return '';
3991  }
3992  var lineBuf                = '';
3993  switch (ch) {
3994  case 0x00: /* ignored */                                              break;
3995  case 0x08: this.bs();                                                 break;
3996  case 0x09: this.ht();                                                 break;
3997  case 0x0A:
3998  case 0x0B:
3999  case 0x0C:
4000  case 0x84: this.lf(); if (!this.crLfMode)                             break;
4001  case 0x0D: this.cr();                                                 break;
4002  case 0x85: this.cr(); this.lf();                                      break;
4003  case 0x0E: this.useGMap     = 1;
4004             this.translate   = this.GMap[1];
4005             this.dispCtrl    = true;                                   break;
4006  case 0x0F: this.useGMap     = 0;
4007             this.translate   = this.GMap[0];
4008             this.dispCtrl    = false;                                  break;
4009  case 0x18:
4010  case 0x1A: this.isEsc       = ESnormal;                               break;
4011  case 0x1B: this.isEsc       = ESesc;                                  break;
4012  case 0x7F: /* ignored */                                              break;
4013  case 0x88: this.userTabStop[this.cursorX] = true;                     break;
4014  case 0x8D: this.ri();                                                 break;
4015  case 0x8E: this.isEsc       = ESss2;                                  break;
4016  case 0x8F: this.isEsc       = ESss3;                                  break;
4017  case 0x9A: this.respondID();                                          break;
4018  case 0x9B: this.isEsc       = ESsquare;                               break;
4019  case 0x07: if (this.isEsc != EStitle) {
4020               this.beep();                                             break;
4021             }
4022             /* fall thru */
4023  default:   switch (this.isEsc) {
4024    case ESesc:
4025      this.isEsc              = ESnormal;
4026      switch (ch) {
4027/*%*/ case 0x25: this.isEsc   = ESpercent;                              break;
4028/*(*/ case 0x28: this.isEsc   = ESsetG0;                                break;
4029/*-*/ case 0x2D:
4030/*)*/ case 0x29: this.isEsc   = ESsetG1;                                break;
4031/*.*/ case 0x2E:
4032/***/ case 0x2A: this.isEsc   = ESsetG2;                                break;
4033/*/*/ case 0x2F:
4034/*+*/ case 0x2B: this.isEsc   = ESsetG3;                                break;
4035/*#*/ case 0x23: this.isEsc   = EShash;                                 break;
4036/*7*/ case 0x37: this.saveCursor();                                     break;
4037/*8*/ case 0x38: this.restoreCursor();                                  break;
4038/*>*/ case 0x3E: this.applKeyMode = false;                              break;
4039/*=*/ case 0x3D: this.applKeyMode = true;                               break;
4040/*D*/ case 0x44: this.lf();                                             break;
4041/*E*/ case 0x45: this.cr(); this.lf();                                  break;
4042/*M*/ case 0x4D: this.ri();                                             break;
4043/*N*/ case 0x4E: this.isEsc   = ESss2;                                  break;
4044/*O*/ case 0x4F: this.isEsc   = ESss3;                                  break;
4045/*H*/ case 0x48: this.userTabStop[this.cursorX] = true;                 break;
4046/*Z*/ case 0x5A: this.respondID();                                      break;
4047/*[*/ case 0x5B: this.isEsc   = ESsquare;                               break;
4048/*]*/ case 0x5D: this.isEsc   = ESnonstd;                               break;
4049/*c*/ case 0x63: this.reset();                                          break;
4050/*g*/ case 0x67: this.flashScreen();                                    break;
4051      default:                                                          break;
4052      }
4053      break;
4054    case ESnonstd:
4055      switch (ch) {
4056/*0*/ case 0x30:
4057/*1*/ case 0x31:
4058/*2*/ case 0x32: this.isEsc   = EStitle; this.titleString = '';         break;
4059/*6*/ case 0x36: this.isEsc   = ESVTEtitle;                             break;
4060/*7*/ case 0x37: this.isEsc   = ESVTEtitle;                             break;
4061/*P*/ case 0x50: this.npar    = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
4062                 this.isEsc   = ESpalette;                              break;
4063/*R*/ case 0x52: // Palette support is not implemented
4064                 this.isEsc   = ESnormal;                               break;
4065      default:   this.isEsc   = ESnormal;                               break;
4066      }
4067      break;
4068    case ESpalette:
4069      if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
4070          (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
4071          (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
4072        this.par[this.npar++] = ch > 0x39  /*9*/ ? (ch & 0xDF) - 55
4073                                                : (ch & 0xF);
4074        if (this.npar == 7) {
4075          // Palette support is not implemented
4076          this.isEsc          = ESnormal;
4077        }
4078      } else {
4079        this.isEsc            = ESnormal;
4080      }
4081      break;
4082    case ESsquare:
4083      this.npar               = 0;
4084      this.par                = [ 0, 0, 0, 0, 0, 0, 0, 0,
4085                                  0, 0, 0, 0, 0, 0, 0, 0 ];
4086      this.isEsc              = ESgetpars;
4087/*[*/ if (ch == 0x5B) { // Function key
4088        this.isEsc            = ESfunckey;
4089        break;
4090      } else {
4091/*?*/   this.isQuestionMark   = ch == 0x3F;
4092        if (this.isQuestionMark) {
4093          break;
4094        }
4095      }
4096      // Fall through
4097    case ESdeviceattr:
4098    case ESgetpars:
4099/*;*/ if (ch == 0x3B) {
4100        this.npar++;
4101        break;
4102      } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
4103        var par               = this.par[this.npar];
4104        if (par == undefined) {
4105          par                 = 0;
4106        }
4107        this.par[this.npar]   = 10*par + (ch & 0xF);
4108        break;
4109      } else if (this.isEsc == ESdeviceattr) {
4110        switch (ch) {
4111/*c*/   case 0x63: if (this.par[0] == 0) this.respondSecondaryDA();     break;
4112/*m*/   case 0x6D: /* (re)set key modifier resource values */           break;
4113/*n*/   case 0x6E: /* disable key modifier resource values */           break;
4114/*p*/   case 0x70: /* set pointer mode resource value */                break;
4115        default:                                                        break;
4116        }
4117        this.isEsc            = ESnormal;
4118        break;
4119      } else {
4120        this.isEsc            = ESgotpars;
4121      }
4122      // Fall through
4123    case ESgotpars:
4124      this.isEsc              = ESnormal;
4125      if (this.isQuestionMark) {
4126        switch (ch) {
4127/*h*/   case 0x68: this.setMode(true);                                  break;
4128/*l*/   case 0x6C: this.setMode(false);                                 break;
4129/*c*/   case 0x63: this.setCursorAttr(this.par[2], this.par[1]);        break;
4130        default:                                                        break;
4131        }
4132        this.isQuestionMark   = false;
4133        break;
4134      }
4135      switch (ch) {
4136/*!*/ case 0x21: this.isEsc   = ESbang;                                 break;
4137/*>*/ case 0x3E: if (!this.npar) this.isEsc  = ESdeviceattr;            break;
4138/*G*/ case 0x47:
4139/*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY);            break;
4140/*A*/ case 0x41: this.gotoXY(this.cursorX,
4141                             this.cursorY - (this.par[0] ? this.par[0] : 1));
4142                                                                        break;
4143/*B*/ case 0x42:
4144/*e*/ case 0x65: this.gotoXY(this.cursorX,
4145                             this.cursorY + (this.par[0] ? this.par[0] : 1));
4146                                                                        break;
4147/*C*/ case 0x43:
4148/*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
4149                             this.cursorY);                             break;
4150/*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
4151                             this.cursorY);                             break;
4152/*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
4153                                                                        break;
4154/*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
4155                                                                        break;
4156/*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1);           break;
4157/*H*/ case 0x48:
4158/*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1);        break;
4159/*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1);                break;
4160/*at*/case 0x40: this.csiAt(this.par[0]);                               break;
4161/*i*/ case 0x69: this.csii(this.par[0]);                                break;
4162/*J*/ case 0x4A: this.csiJ(this.par[0]);                                break;
4163/*K*/ case 0x4B: this.csiK(this.par[0]);                                break;
4164/*L*/ case 0x4C: this.csiL(this.par[0]);                                break;
4165/*M*/ case 0x4D: this.csiM(this.par[0]);                                break;
4166/*m*/ case 0x6D: this.csim();                                           break;
4167/*P*/ case 0x50: this.csiP(this.par[0]);                                break;
4168/*X*/ case 0x58: this.csiX(this.par[0]);                                break;
4169/*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1);                break;
4170/*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1);                break;
4171/*c*/ case 0x63: if (!this.par[0]) this.respondID();                    break;
4172/*g*/ case 0x67: if (this.par[0] == 0) {
4173                   this.userTabStop[this.cursorX] = false;
4174                 } else if (this.par[0] == 2 || this.par[0] == 3) {
4175                   this.userTabStop               = [ ];
4176                   for (var i = 0; i < this.terminalWidth; i++) {
4177                     this.userTabStop[i]          = false;
4178                   }
4179                 }
4180                 break;
4181/*h*/ case 0x68: this.setMode(true);                                    break;
4182/*l*/ case 0x6C: this.setMode(false);                                   break;
4183/*n*/ case 0x6E: switch (this.par[0]) {
4184                 case 5: this.statusReport();                           break;
4185                 case 6: this.cursorReport();                           break;
4186                 default:                                               break;
4187                 }
4188                 break;
4189/*q*/ case 0x71: // LED control not implemented
4190                                                                        break;
4191/*r*/ case 0x72: var t        = this.par[0] ? this.par[0] : 1;
4192                 var b        = this.par[1] ? this.par[1]
4193                                            : this.terminalHeight;
4194                 if (t < b && b <= this.terminalHeight) {
4195                   this.top   = t - 1;
4196                   this.bottom= b;
4197                   this.gotoXaY(0, 0);
4198                 }
4199                 break;
4200/*b*/ case 0x62: var c        = this.par[0] ? this.par[0] : 1;
4201                 if (c > this.terminalWidth * this.terminalHeight) {
4202                   c          = this.terminalWidth * this.terminalHeight;
4203                 }
4204                 while (c-- > 0) {
4205                   lineBuf   += this.lastCharacter;
4206                 }
4207                 break;
4208/*s*/ case 0x73: this.saveCursor();                                     break;
4209/*u*/ case 0x75: this.restoreCursor();                                  break;
4210/*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1);                break;
4211/*]*/ case 0x5D: this.settermCommand();                                 break;
4212      default:                                                          break;
4213      }
4214      break;
4215    case ESbang:
4216      if (ch == 'p') {
4217        this.reset();
4218      }
4219      this.isEsc              = ESnormal;
4220      break;
4221    case ESpercent:
4222      this.isEsc              = ESnormal;
4223      switch (ch) {
4224/*at*/case 0x40: this.utfEnabled = false;                               break;
4225/*G*/ case 0x47:
4226/*8*/ case 0x38: this.utfEnabled = true;                                break;
4227      default:                                                          break;
4228      }
4229      break;
4230    case ESfunckey:
4231      this.isEsc              = ESnormal;                               break;
4232    case EShash:
4233      this.isEsc              = ESnormal;
4234/*8*/ if (ch == 0x38) {
4235        // Screen alignment test not implemented
4236      }
4237      break;
4238    case ESsetG0:
4239    case ESsetG1:
4240    case ESsetG2:
4241    case ESsetG3:
4242      var g                   = this.isEsc - ESsetG0;
4243      this.isEsc              = ESnormal;
4244      switch (ch) {
4245/*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap;                  break;
4246/*A*/ case 0x42:
4247/*B*/ case 0x42: this.GMap[g] = this.Latin1Map;                         break;
4248/*U*/ case 0x55: this.GMap[g] = this.CodePage437Map;                    break;
4249/*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap;                   break;
4250      default:                                                          break;
4251      }
4252      if (this.useGMap == g) {
4253        this.translate        = this.GMap[g];
4254      }
4255      break;
4256    case EStitle:
4257      if (ch == 0x07) {
4258        if (this.titleString && this.titleString.charAt(0) == ';') {
4259          this.titleString    = this.titleString.substr(1);
4260          if (this.titleString != '') {
4261            this.titleString += ' - ';
4262          }
4263          this.titleString += 'Shell In A Box'
4264        }
4265        try {
4266          window.document.title = this.titleString;
4267        } catch (e) {
4268        }
4269        this.isEsc            = ESnormal;
4270      } else {
4271        this.titleString     += String.fromCharCode(ch);
4272      }
4273      break;
4274    case ESss2:
4275    case ESss3:
4276      if (ch < 256) {
4277          ch                  = this.GMap[this.isEsc - ESss2 + 2]
4278                                         [this.toggleMeta ? (ch | 0x80) : ch];
4279        if ((ch & 0xFF00) == 0xF000) {
4280          ch                  = ch & 0xFF;
4281        } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4282          this.isEsc         = ESnormal;                                break;
4283        }
4284      }
4285      this.lastCharacter      = String.fromCharCode(ch);
4286      lineBuf                += this.lastCharacter;
4287      this.isEsc              = ESnormal;                               break;
4288    case ESVTEtitle:
4289      // Ignores VTE escape sequences for current directory (OSC6) and current
4290      // file (OSC7).
4291      if (ch == 0x07 || ch == 0x5C) {
4292        this.isEsc            = ESnormal;
4293      }
4294      break;
4295    default:
4296      this.isEsc              = ESnormal;                               break;
4297    }
4298    break;
4299  }
4300  return lineBuf;
4301};
4302
4303VT100.prototype.renderString = function(s, showCursor) {
4304  if (this.printing) {
4305    this.sendToPrinter(s);
4306    if (showCursor) {
4307      this.showCursor();
4308    }
4309    return;
4310  }
4311
4312  // We try to minimize the number of DOM operations by coalescing individual
4313  // characters into strings. This is a significant performance improvement.
4314  var incX = s.length;
4315  if (incX > this.terminalWidth - this.cursorX) {
4316    incX   = this.terminalWidth - this.cursorX;
4317    if (incX <= 0) {
4318      return;
4319    }
4320    s      = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4321  }
4322  if (showCursor) {
4323    // Minimize the number of calls to putString(), by avoiding a direct
4324    // call to this.showCursor()
4325    this.cursor.style.visibility = '';
4326  }
4327  this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4328};
4329
4330VT100.prototype.vt100 = function(s) {
4331  this.cursorNeedsShowing = this.hideCursor();
4332  this.respondString      = '';
4333  var lineBuf             = '';
4334  for (var i = 0; i < s.length; i++) {
4335    var ch = s.charCodeAt(i);
4336    if (this.utfEnabled) {
4337      // Decode UTF8 encoded character
4338      if (ch > 0x7F) {
4339        if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4340          this.utfChar    = (this.utfChar << 6) | (ch & 0x3F);
4341          if (--this.utfCount <= 0) {
4342            if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4343              ch = 0xFFFD;
4344            } else {
4345              ch          = this.utfChar;
4346            }
4347          } else {
4348            continue;
4349          }
4350        } else {
4351          if ((ch & 0xE0) == 0xC0) {
4352            this.utfCount = 1;
4353            this.utfChar  = ch & 0x1F;
4354          } else if ((ch & 0xF0) == 0xE0) {
4355            this.utfCount = 2;
4356            this.utfChar  = ch & 0x0F;
4357          } else if ((ch & 0xF8) == 0xF0) {
4358            this.utfCount = 3;
4359            this.utfChar  = ch & 0x07;
4360          } else if ((ch & 0xFC) == 0xF8) {
4361            this.utfCount = 4;
4362            this.utfChar  = ch & 0x03;
4363          } else if ((ch & 0xFE) == 0xFC) {
4364            this.utfCount = 5;
4365            this.utfChar  = ch & 0x01;
4366          } else {
4367            this.utfCount = 0;
4368          }
4369          continue;
4370        }
4371      } else {
4372        this.utfCount     = 0;
4373      }
4374    }
4375    var isNormalCharacter =
4376      (ch >= 32 && ch <= 127 || ch >= 160 ||
4377       this.utfEnabled && ch >= 128 ||
4378       !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4379      (ch != 0x7F || this.dispCtrl);
4380
4381    if (isNormalCharacter && this.isEsc == ESnormal) {
4382      if (ch < 256) {
4383        ch                = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4384      }
4385      if ((ch & 0xFF00) == 0xF000) {
4386        ch                = ch & 0xFF;
4387      } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4388        continue;
4389      }
4390      if (!this.printing) {
4391        if (this.needWrap || this.insertMode) {
4392          if (lineBuf) {
4393            this.renderString(lineBuf);
4394            lineBuf       = '';
4395          }
4396        }
4397        if (this.needWrap) {
4398          this.cr(); this.lf();
4399        }
4400        if (this.insertMode) {
4401          this.scrollRegion(this.cursorX, this.cursorY,
4402                            this.terminalWidth - this.cursorX - 1, 1,
4403                            1, 0, this.color, this.style);
4404        }
4405      }
4406      this.lastCharacter  = String.fromCharCode(ch);
4407      lineBuf            += this.lastCharacter;
4408      if (!this.printing &&
4409          this.cursorX + lineBuf.length >= this.terminalWidth) {
4410        this.needWrap     = this.autoWrapMode;
4411      }
4412    } else {
4413      if (lineBuf) {
4414        this.renderString(lineBuf);
4415        lineBuf           = '';
4416      }
4417      var expand          = this.doControl(ch);
4418      if (expand.length) {
4419        var r             = this.respondString;
4420        this.respondString= r + this.vt100(expand);
4421      }
4422    }
4423  }
4424  if (lineBuf) {
4425    this.renderString(lineBuf, this.cursorNeedsShowing);
4426  } else if (this.cursorNeedsShowing) {
4427    this.showCursor();
4428  }
4429  return this.respondString;
4430};
4431
4432VT100.prototype.Latin1Map = [
44330x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
44340x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
44350x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
44360x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
44370x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
44380x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
44390x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
44400x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
44410x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
44420x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
44430x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
44440x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
44450x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
44460x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
44470x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
44480x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
44490x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
44500x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
44510x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
44520x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
44530x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
44540x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
44550x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
44560x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
44570x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
44580x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
44590x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
44600x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
44610x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
44620x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
44630x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
44640x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4465];
4466
4467VT100.prototype.VT100GraphicsMap = [
44680x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
44690x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
44700x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
44710x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
44720x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
44730x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
44740x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
44750x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
44760x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
44770x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
44780x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
44790x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
44800x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
44810x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
44820xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
44830x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
44840x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
44850x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
44860x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
44870x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
44880x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
44890x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
44900x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
44910x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
44920x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
44930x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
44940x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
44950x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
44960x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
44970x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
44980x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
44990x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4500];
4501
4502VT100.prototype.CodePage437Map = [
45030x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
45040x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
45050x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
45060x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
45070x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
45080x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
45090x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
45100x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
45110x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
45120x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
45130x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
45140x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
45150x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
45160x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
45170x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
45180x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
45190x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
45200x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
45210x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
45220x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
45230x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
45240x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
45250x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
45260x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
45270x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
45280x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
45290x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
45300x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
45310x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
45320x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
45330x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
45340x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4535];
4536
4537VT100.prototype.DirectToFontMap = [
45380xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
45390xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
45400xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
45410xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
45420xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
45430xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
45440xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
45450xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
45460xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
45470xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
45480xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
45490xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
45500xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
45510xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
45520xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
45530xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
45540xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
45550xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
45560xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
45570xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
45580xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
45590xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
45600xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
45610xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
45620xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
45630xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
45640xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
45650xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
45660xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
45670xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
45680xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
45690xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4570];
4571
4572VT100.prototype.ctrlAction = [
4573  true,  false, false, false, false, false, false, true,
4574  true,  true,  true,  true,  true,  true,  true,  true,
4575  false, false, false, false, false, false, false, false,
4576  true,  false, true,  true,  false, false, false, false
4577];
4578
4579VT100.prototype.ctrlAlways = [
4580  true,  false, false, false, false, false, false, false,
4581  true,  false, true,  false, true,  true,  true,  true,
4582  false, false, false, false, false, false, false, false,
4583  false, false, false, true,  false, false, false, false
4584];
4585
4586/* vim: set filetype=javascript : */
4587