text: serverSupportsSSL = false;
  disableSSLMenu    = false;
  suppressAllAudio  = false;
  linkifyURLs       = 1;
  userCSSList       = [  ];
  serverMessagesOrigin = false;
  
  // VT100.js -- JavaScript based terminal emulator
  // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
  //
  // This program is free software; you can redistribute it and/or modify
  // it under the terms of the GNU General Public License version 2 as
  // published by the Free Software Foundation.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along
  // with this program; if not, write to the Free Software Foundation, Inc.,
  // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  //
  // In addition to these license terms, the author grants the following
  // additional rights:
  //
  // If you modify this program, or any covered work, by linking or
  // combining it with the OpenSSL project's OpenSSL library (or a
  // modified version of that library), containing parts covered by the
  // terms of the OpenSSL or SSLeay licenses, the author
  // grants you additional permission to convey the resulting work.
  // Corresponding Source for a non-source form of such a combination
  // shall include the source code for the parts of OpenSSL used as well
  // as that of the covered work.
  //
  // You may at your option choose to remove this additional permission from
  // the work, or from any part of it.
  //
  // It is possible to build this program in a way that it loads OpenSSL
  // libraries at run-time. If doing so, the following notices are required
  // by the OpenSSL and SSLeay licenses:
  //
  // This product includes software developed by the OpenSSL Project
  // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
  //
  // This product includes cryptographic software written by Eric Young
  // (eay@cryptsoft.com)
  //
  //
  // The most up-to-date version of this program is always available from
  // http://shellinabox.com
  //
  //
  // Notes:
  //
  // The author believes that for the purposes of this license, you meet the
  // requirements for publishing the source code, if your web server publishes
  // the source in unmodified form (i.e. with licensing information, comments,
  // formatting, and identifier names intact). If there are technical reasons
  // that require you to make changes to the source code when serving the
  // JavaScript (e.g to remove pre-processor directives from the source), these
  // changes should be done in a reversible fashion.
  //
  // The author does not consider websites that reference this script in
  // unmodified form, and web servers that serve this script in unmodified form
  // to be derived works. As such, they are believed to be outside of the
  // scope of this license and not subject to the rights or restrictions of the
  // GNU General Public License.
  //
  // If in doubt, consult a legal professional familiar with the laws that
  // apply in your country.
  
  // #define ESnormal        0
  // #define ESesc           1
  // #define ESsquare        2
  // #define ESgetpars       3
  // #define ESgotpars       4
  // #define ESdeviceattr    5
  // #define ESfunckey       6
  // #define EShash          7
  // #define ESsetG0         8
  // #define ESsetG1         9
  // #define ESsetG2        10
  // #define ESsetG3        11
  // #define ESbang         12
  // #define ESpercent      13
  // #define ESignore       14
  // #define ESnonstd       15
  // #define ESpalette      16
  // #define EStitle        17
  // #define ESss2          18
  // #define ESss3          19
  
  // #define ATTR_DEFAULT   0x60F0
  // #define ATTR_REVERSE   0x0100
  // #define ATTR_UNDERLINE 0x0200
  // #define ATTR_DIM       0x0400
  // #define ATTR_BRIGHT    0x0800
  // #define ATTR_BLINK     0x1000
  // #define ATTR_DEF_FG    0x2000
  // #define ATTR_DEF_BG    0x4000
  
  // #define MOUSE_DOWN     0
  // #define MOUSE_UP       1
  // #define MOUSE_CLICK    2
  
  function VT100(container) {
    if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
      this.urlRE            = null;
    } else {
      this.urlRE            = new RegExp(
      // Known URL protocol are "http", "https", and "ftp".
      '(?:http|https|ftp)://' +
  
      // Optionally allow username and passwords.
      '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
  
      // Hostname.
      '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
      '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
      '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
  
      // Port
      '(?::[1-9][0-9]*)?' +
  
      // Path.
      '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
  
      (linkifyURLs <= 1 ? '' :
      // Also support URLs without a protocol (assume "http").
      // Optional username and password.
      '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
  
      // Hostnames must end with a well-known top-level domain or must be
      // numeric.
      '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
      'localhost|' +
      '(?:(?!-)' +
          '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
      '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
      'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
      'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
      'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
      'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
      'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
      'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
      'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
      'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
      'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
      'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
      'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
      'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
  
      // Port
      '(?::[1-9][0-9]{0,4})?' +
  
      // Path.
      '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
  
      // In addition, support e-mail address. Optionally, recognize "mailto:"
      '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
  
      // Username:
      '[-_.+a-zA-Z0-9]+@' +
  
      // Hostname.
      '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
      '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
      'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
      'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
      'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
      'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
      'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
      'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
      'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
      'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
      'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
      'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
      'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
      'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
  
      // Optional arguments
      '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
    }
    this.getUserSettings();
    this.initializeElements(container);
    this.maxScrollbackLines = 2000;
    this.npar               = 0;
    this.par                = [ ];
    this.isQuestionMark     = false;
    this.savedX             = [ ];
    this.savedY             = [ ];
    this.savedAttr          = [ ];
    this.savedAttrFg        = [ ];
    this.savedAttrBg        = [ ];
    this.savedUseGMap       = 0;
    this.savedGMap          = [ this.Latin1Map, this.VT100GraphicsMap,
                                this.CodePage437Map, this.DirectToFontMap ];
    this.savedValid         = [ ];
    this.respondString      = '';
    this.titleString        = '';
    this.internalClipboard  = undefined;
    this.reset(true);
  }
  
  VT100.prototype.reset = function(clearHistory) {
    this.isEsc                                         = 0 /* ESnormal */;
    this.needWrap                                      = false;
    this.autoWrapMode                                  = true;
    this.dispCtrl                                      = false;
    this.toggleMeta                                    = false;
    this.insertMode                                    = false;
    this.applKeyMode                                   = false;
    this.cursorKeyMode                                 = false;
    this.crLfMode                                      = false;
    this.offsetMode                                    = false;
    this.mouseReporting                                = false;
    this.printing                                      = false;
    if (typeof this.printWin != 'undefined' &&
        this.printWin && !this.printWin.closed) {
      this.printWin.close();
    }
    this.printWin                                      = null;
    this.utfEnabled                                    = this.utfPreferred;
    this.utfCount                                      = 0;
    this.utfChar                                       = 0;
    this.color                                         = 'ansiDef bgAnsiDef';
    this.style                                         = '';
    this.attr                                          = 0x60F0 /* ATTR_DEFAULT */;
    this.attrFg                                        = false;
    this.attrBg                                        = false;
    this.useGMap                                       = 0;
    this.GMap                                          = [ this.Latin1Map,
                                                           this.VT100GraphicsMap,
                                                           this.CodePage437Map,
                                                           this.DirectToFontMap];
    this.translate                                     = this.GMap[this.useGMap];
    this.top                                           = 0;
    this.bottom                                        = this.terminalHeight;
    this.lastCharacter                                 = ' ';
    this.userTabStop                                   = [ ];
  
    if (clearHistory) {
      for (var i = 0; i < 2; i++) {
        while (this.console[i].firstChild) {
          this.console[i].removeChild(this.console[i].firstChild);
        }
      }
    }
  
    this.enableAlternateScreen(false);
  
    var wasCompressed                                  = false;
    var transform                                      = this.getTransformName();
    if (transform) {
      for (var i = 0; i < 2; ++i) {
        wasCompressed                  |= this.console[i].style[transform] != '';
        this.console[i].style[transform]               = '';
      }
      this.cursor.style[transform]                     = '';
      this.space.style[transform]                      = '';
      if (transform == 'filter') {
        this.console[this.currentScreen].style.width   = '';
      }
    }
    this.scale                                         = 1.0;
    if (wasCompressed) {
      this.resizer();
    }
  
    this.gotoXY(0, 0);
    this.showCursor();
    this.isInverted                                    = false;
    this.refreshInvertedState();
    this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
                     this.color, this.style);
  };
  
  VT100.prototype.addListener = function(elem, event, listener) {
    try {
      if (elem.addEventListener) {
        elem.addEventListener(event, listener, false);
      } else {
        elem.attachEvent('on' + event, listener);
      }
    } catch (e) {
    }
  };
  
  VT100.prototype.getUserSettings = function() {
    // Compute hash signature to identify the entries in the userCSS menu.
    // If the menu is unchanged from last time, default values can be
    // looked up in a cookie associated with this page.
    this.signature            = 3;
    this.utfPreferred         = true;
    this.visualBell           = typeof suppressAllAudio != 'undefined' &&
                                suppressAllAudio;
    this.autoprint            = true;
    this.softKeyboard         = false;
    this.blinkingCursor       = true;
    this.disableAlt           = false;
  
    if (navigator.platform.indexOf("Mac") != -1) {
      this.disableAlt         = true;
    }
  
    if (this.visualBell) {
      this.signature          = Math.floor(16807*this.signature + 1) %
                                           ((1 << 31) - 1);
    }
    if (typeof userCSSList != 'undefined') {
      for (var i = 0; i < userCSSList.length; ++i) {
        var label             = userCSSList[i][0];
        for (var j = 0; j < label.length; ++j) {
          this.signature      = Math.floor(16807*this.signature+
                                           label.charCodeAt(j)) %
                                           ((1 << 31) - 1);
        }
        if (userCSSList[i][1]) {
          this.signature      = Math.floor(16807*this.signature + 1) %
                                           ((1 << 31) - 1);
        }
      }
    }
  
    var key                   = 'shellInABox=' + this.signature + ':';
    var settings              = document.cookie.indexOf(key);
    if (settings >= 0) {
      settings                = document.cookie.substr(settings + key.length).
                                                     replace(/([0-1]*).*/, "$1");
      if (settings.length == 6 + (typeof userCSSList == 'undefined' ?
                                  0 : userCSSList.length)) {
        this.utfPreferred     = settings.charAt(0) != '0';
        this.visualBell       = settings.charAt(1) != '0';
        this.autoprint        = settings.charAt(2) != '0';
        this.softKeyboard     = settings.charAt(3) != '0';
        this.blinkingCursor   = settings.charAt(4) != '0';
        this.disableAlt       = settings.charAt(5) != '0';
        if (typeof userCSSList != 'undefined') {
          for (var i = 0; i < userCSSList.length; ++i) {
            userCSSList[i][2] = settings.charAt(i + 6) != '0';
          }
        }
      }
    }
    this.utfEnabled           = this.utfPreferred;
  };
  
  VT100.prototype.storeUserSettings = function() {
    var settings  = 'shellInABox=' + this.signature + ':' +
                    (this.utfEnabled     ? '1' : '0') +
                    (this.visualBell     ? '1' : '0') +
                    (this.autoprint      ? '1' : '0') +
                    (this.softKeyboard   ? '1' : '0') +
                    (this.blinkingCursor ? '1' : '0') +
                    (this.disableAlt     ? '1' : '0');
    if (typeof userCSSList != 'undefined') {
      for (var i = 0; i < userCSSList.length; ++i) {
        settings += userCSSList[i][2] ? '1' : '0';
      }
    }
    var d         = new Date();
    d.setDate(d.getDate() + 3653);
    document.cookie = settings + ';expires=' + d.toGMTString();
  };
  
  VT100.prototype.initializeUserCSSStyles = function() {
    this.usercssActions                    = [];
    if (typeof userCSSList != 'undefined') {
      var menu                             = '';
      var group                            = '';
      var wasSingleSel                     = 1;
      var beginOfGroup                     = 0;
      for (var i = 0; i <= userCSSList.length; ++i) {
        if (i < userCSSList.length) {
          var label                        = userCSSList[i][0];
          var newGroup                     = userCSSList[i][1];
          var enabled                      = userCSSList[i][2];
  
          // Add user style sheet to document
          var style                        = document.createElement('link');
          style.setAttribute('id',           'usercss-' + i);
          style.setAttribute('href',         'usercss-' + i + '.css');
          style.setAttribute('rel',          'stylesheet');
          style.setAttribute('type',         'text/css');
          document.getElementsByTagName('head')[0].appendChild(style);
  
          // If stylesheet needs to be disabled we need to do that from onload
          // event, otherwise 'disabled' attribute will be ignored.
          if (!enabled) {
            if ('onload' in style) {
              style.onload                     = function(style) {
                return function () {
                  style.disabled               = true;
                }
              }(style);
            } else {
              // If onload event is not supported we will try to do it the old
              // way. This also works sometimes, mosty in cases when browser
              // already has cached version of stylesheet.
              style.disabled                   = true;
            }
          }
        }
  
        // Add entry to menu
        if (newGroup || i == userCSSList.length) {
          if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
            // The last group had multiple entries that are mutually exclusive;
            // or the previous to last group did. In either case, we need to
            // append a "<hr />" before we can add the last group to the menu.
            menu                          += '<hr />';
          }
          wasSingleSel                     = i - beginOfGroup < 1;
          menu                            += group;
          group                            = '';
  
          for (var j = beginOfGroup; j < i; ++j) {
            this.usercssActions[this.usercssActions.length] =
              function(vt100, current, begin, count) {
  
                // Deselect all other entries in the group, then either select
                // (for multiple entries in group) or toggle (for on/off entry)
                // the current entry.
                return function() {
                  var entry                = vt100.getChildById(vt100.menu,
                                                                'beginusercss');
                  var i                    = -1;
                  var j                    = -1;
                  for (var c = count; c > 0; ++j) {
                    if (entry.tagName == 'LI') {
                      if (++i >= begin) {
                        --c;
                        var label          = vt100.usercss.childNodes[j];
  
                        // Restore label to just the text content
                        if (typeof label.textContent == 'undefined') {
                          var s            = label.innerText;
                          label.innerHTML  = '';
                          label.appendChild(document.createTextNode(s));
                        } else {
                          label.textContent= label.textContent;
                        }
  
                        // User style sheets are numbered sequentially
                        var sheet          = document.getElementById(
                                                                 'usercss-' + i);
                        if (i == current) {
                          if (count == 1) {
                            sheet.disabled = !sheet.disabled;
                          } else {
                            sheet.disabled = false;
                          }
                          if (!sheet.disabled) {
                            label.innerHTML= '<img src="enabled.gif" />' +
                                             label.innerHTML;
                          }
                        } else {
                          sheet.disabled   = true;
                        }
                        userCSSList[i][2]  = !sheet.disabled;
                      }
                    }
                    entry                  = entry.nextSibling;
                  }
  
                  // If the font size changed, adjust cursor and line dimensions
                  this.cursor.style.cssText= '';
                  this.cursorWidth         = this.cursor.clientWidth;
                  this.cursorHeight        = this.lineheight.clientHeight;
                  for (i = 0; i < this.console.length; ++i) {
                    for (var line = this.console[i].firstChild; line;
                         line = line.nextSibling) {
                      line.style.height    = this.cursorHeight + 'px';
                    }
                  }
                  vt100.resizer();
                };
              }(this, j, beginOfGroup, i - beginOfGroup);
          }
  
          if (i == userCSSList.length) {
            break;
          }
  
          beginOfGroup                     = i;
        }
        // Collect all entries in a group, before attaching them to the menu.
        // This is necessary as we don't know whether this is a group of
        // mutually exclusive options (which should be separated by "<hr />" on
        // both ends), or whether this is a on/off toggle, which can be grouped
        // together with other on/off options.
        group                             +=
          '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
                   label +
          '</li>';
      }
      this.usercss.innerHTML               = menu;
    }
  };
  
  VT100.prototype.resetLastSelectedKey = function(e) {
    var key                          = this.lastSelectedKey;
    if (!key) {
      return false;
    }
  
    var position                     = this.mousePosition(e);
  
    // We don't get all the necessary events to reliably reselect a key
    // if we moved away from it and then back onto it. We approximate the
    // behavior by remembering the key until either we release the mouse
    // button (we might never get this event if the mouse has since left
    // the window), or until we move away too far.
    var box                          = this.keyboard.firstChild;
    if (position[0] <  box.offsetLeft + key.offsetWidth ||
        position[1] <  box.offsetTop + key.offsetHeight ||
        position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
        position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
        position[0] <  box.offsetLeft + key.offsetLeft - key.offsetWidth ||
        position[1] <  box.offsetTop + key.offsetTop - key.offsetHeight ||
        position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
        position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
      if (this.lastSelectedKey.className) log.console('reset: deselecting');
      this.lastSelectedKey.className = '';
      this.lastSelectedKey           = undefined;
    }
    return false;
  };
  
  VT100.prototype.showShiftState = function(state) {
    var style              = document.getElementById('shift_state');
    if (state) {
      this.setTextContentRaw(style,
                             '#vt100 #keyboard .shifted {' +
                               'display: inline }' +
                             '#vt100 #keyboard .unshifted {' +
                               'display: none }');
    } else {
      this.setTextContentRaw(style, '');
    }
    var elems              = this.keyboard.getElementsByTagName('I');
    for (var i = 0; i < elems.length; ++i) {
      if (elems[i].id == '16') {
        elems[i].className = state ? 'selected' : '';
      }
    }
  };
  
  VT100.prototype.showCtrlState = function(state) {
    var ctrl         = this.getChildById(this.keyboard, '17' /* Ctrl */);
    if (ctrl) {
      ctrl.className = state ? 'selected' : '';
    }
  };
  
  VT100.prototype.showAltState = function(state) {
    var alt         = this.getChildById(this.keyboard, '18' /* Alt */);
    if (alt) {
      alt.className = state ? 'selected' : '';
    }
  };
  
  VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
    var fake      = [ ];
    fake.charCode = ch;
    fake.keyCode  = key;
    fake.ctrlKey  = ctrl;
    fake.shiftKey = shift;
    fake.altKey   = alt;
    fake.metaKey  = alt;
    return this.handleKey(fake);
  };
  
  VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
    if (elem == undefined) {
      return;
    }
    if (ch == '\u00A0') {
      // &nbsp; should be treated as a regular space character.
      ch                                  = ' ';
    }
    if (ch != undefined && CH == undefined) {
      // For letter keys, we automatically compute the uppercase character code
      // from the lowercase one.
      CH                                  = ch.toUpperCase();
    }
    if (KEY == undefined && key != undefined) {
      // Most keys have identically key codes for both lowercase and uppercase
      // keypresses. Normally, only function keys would have distinct key codes,
      // whereas regular keys have character codes.
      KEY                                 = key;
    } else if (KEY == undefined && CH != undefined) {
      // For regular keys, copy the character code to the key code.
      KEY                                 = CH.charCodeAt(0);
    }
    if (key == undefined && ch != undefined) {
      // For regular keys, copy the character code to the key code.
      key                                 = ch.charCodeAt(0);
    }
    // Convert characters to numeric character codes. If the character code
    // is undefined (i.e. this is a function key), set it to zero.
    ch                                    = ch ? ch.charCodeAt(0) : 0;
    CH                                    = CH ? CH.charCodeAt(0) : 0;
  
    // Mouse down events high light the key. We also set lastSelectedKey. This
    // is needed to that mouseout/mouseover can keep track of the key that
    // is currently being clicked.
    this.addListener(elem, 'mousedown',
      function(vt100, elem, key) { return function(e) {
        if ((e.which || e.button) == 1) {
          if (vt100.lastSelectedKey) {
            vt100.lastSelectedKey.className= '';
          }
          // Highlight the key while the mouse button is held down.
          if (key == 16 /* Shift */) {
            if (!elem.className != vt100.isShift) {
              vt100.showShiftState(!vt100.isShift);
            }
          } else if (key == 17 /* Ctrl */) {
            if (!elem.className != vt100.isCtrl) {
              vt100.showCtrlState(!vt100.isCtrl);
            }
          } else if (key == 18 /* Alt */) {
            if (!elem.className != vt100.isAlt) {
              vt100.showAltState(!vt100.isAlt);
            }
          } else {
            elem.className                  = 'selected';
          }
          vt100.lastSelectedKey             = elem;
        }
        return false; }; }(this, elem, key));
    var clicked                           =
      // Modifier keys update the state of the keyboard, but do not generate
      // any key clicks that get forwarded to the application.
      key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
      function(vt100, elem) { return function(e) {
        if (elem == vt100.lastSelectedKey) {
          if (key == 16 /* Shift */) {
            // The user clicked the Shift key
            vt100.isShift                 = !vt100.isShift;
            vt100.showShiftState(vt100.isShift);
          } else if (key == 17 /* Ctrl */) {
            vt100.isCtrl                  = !vt100.isCtrl;
            vt100.showCtrlState(vt100.isCtrl);
          } else if (key == 18 /* Alt */) {
            vt100.isAlt                   = !vt100.isAlt;
            vt100.showAltState(vt100.isAlt);
          }
          vt100.lastSelectedKey           = undefined;
        }
        if (vt100.lastSelectedKey) {
          vt100.lastSelectedKey.className = '';
          vt100.lastSelectedKey           = undefined;
        }
        return false; }; }(this, elem) :
      // Regular keys generate key clicks, when the mouse button is released or
      // when a mouse click event is received.
      function(vt100, elem, ch, key, CH, KEY) { return function(e) {
        if (vt100.lastSelectedKey) {
          if (elem == vt100.lastSelectedKey) {
            // The user clicked a key.
            if (vt100.isShift) {
              vt100.clickedKeyboard(e, elem, CH, KEY,
                                    true, vt100.isCtrl, vt100.isAlt);
            } else {
              vt100.clickedKeyboard(e, elem, ch, key,
                                    false, vt100.isCtrl, vt100.isAlt);
            }
            vt100.isShift                 = false;
            vt100.showShiftState(false);
            vt100.isCtrl                  = false;
            vt100.showCtrlState(false);
            vt100.isAlt                   = false;
            vt100.showAltState(false);
          }
          vt100.lastSelectedKey.className = '';
          vt100.lastSelectedKey           = undefined;
        }
        elem.className                    = '';
        return false; }; }(this, elem, ch, key, CH, KEY);
    this.addListener(elem, 'mouseup', clicked);
    this.addListener(elem, 'click', clicked);
  
    // When moving the mouse away from a key, check if any keys need to be
    // deselected.
    this.addListener(elem, 'mouseout',
      function(vt100, elem, key) { return function(e) {
        if (key == 16 /* Shift */) {
          if (!elem.className == vt100.isShift) {
            vt100.showShiftState(vt100.isShift);
          }
        } else if (key == 17 /* Ctrl */) {
          if (!elem.className == vt100.isCtrl) {
            vt100.showCtrlState(vt100.isCtrl);
          }
        } else if (key == 18 /* Alt */) {
          if (!elem.className == vt100.isAlt) {
            vt100.showAltState(vt100.isAlt);
          }
        } else if (elem.className) {
          elem.className                  = '';
          vt100.lastSelectedKey           = elem;
        } else if (vt100.lastSelectedKey) {
          vt100.resetLastSelectedKey(e);
        }
        return false; }; }(this, elem, key));
  
    // When moving the mouse over a key, select it if the user is still holding
    // the mouse button down (i.e. elem == lastSelectedKey)
    this.addListener(elem, 'mouseover',
      function(vt100, elem, key) { return function(e) {
        if (elem == vt100.lastSelectedKey) {
          if (key == 16 /* Shift */) {
            if (!elem.className != vt100.isShift) {
              vt100.showShiftState(!vt100.isShift);
            }
          } else if (key == 17 /* Ctrl */) {
            if (!elem.className != vt100.isCtrl) {
              vt100.showCtrlState(!vt100.isCtrl);
            }
          } else if (key == 18 /* Alt */) {
            if (!elem.className != vt100.isAlt) {
              vt100.showAltState(!vt100.isAlt);
            }
          } else if (!elem.className) {
            elem.className                = 'selected';
          }
        } else {
          vt100.resetLastSelectedKey(e);
        }
        return false; }; }(this, elem, key));
  };
  
  VT100.prototype.initializeKeyBindings = function(elem) {
    if (elem) {
      if (elem.nodeName == "I" || elem.nodeName == "B") {
        if (elem.id) {
          // Function keys. The Javascript keycode is part of the "id"
          var i     = parseInt(elem.id);
          if (i) {
            // If the id does not parse as a number, it is not a keycode.
            this.addKeyBinding(elem, undefined, i);
          }
        } else {
          var child = elem.firstChild;
          if (child) {
            if (child.nodeName == "#text") {
              // If the key only has a text node as a child, then it is a letter.
              // Automatically compute the lower and upper case version of the
              // key.
              var text = this.getTextContent(child) ||
                         this.getTextContent(elem);
              this.addKeyBinding(elem, text.toLowerCase());
            } else if (child.nextSibling) {
              // If the key has two children, they are the lower and upper case
              // character code, respectively.
              this.addKeyBinding(elem, this.getTextContent(child), undefined,
                                 this.getTextContent(child.nextSibling));
            }
          }
        }
      }
    }
    // Recursively parse all other child nodes.
    for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
      this.initializeKeyBindings(elem);
    }
  };
  
  VT100.prototype.initializeKeyboardButton = function() {
    // Configure mouse event handlers for button that displays/hides keyboard
    this.addListener(this.keyboardImage, 'click',
      function(vt100) { return function(e) {
        if (vt100.keyboard.style.display != '') {
          if (vt100.reconnectBtn.style.visibility != '') {
            vt100.initializeKeyboard();
            vt100.showSoftKeyboard();
          }
        } else {
          vt100.hideSoftKeyboard();
          vt100.input.focus();
        }
        return false; }; }(this));
  
    // Enable button that displays keyboard
    if (this.softKeyboard) {
      this.keyboardImage.style.visibility = 'visible';
    }
  };
  
  VT100.prototype.initializeKeyboard = function() {
    // Only need to initialize the keyboard the very first time. When doing so,
    // copy the keyboard layout from the iframe.
    if (this.keyboard.firstChild) {
      return;
    }
    this.keyboard.innerHTML               =
                                      this.layout.contentDocument.body.innerHTML;
    var box                               = this.keyboard.firstChild;
    this.hideSoftKeyboard();
  
    // Configure mouse event handlers for on-screen keyboard
    this.addListener(this.keyboard, 'click',
      function(vt100) { return function(e) {
        vt100.hideSoftKeyboard();
        vt100.input.focus();
        return false; }; }(this));
    this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
    this.addListener(box, 'click', this.cancelEvent);
    this.addListener(box, 'mouseup',
      function(vt100) { return function(e) {
        if (vt100.lastSelectedKey) {
          vt100.lastSelectedKey.className = '';
          vt100.lastSelectedKey           = undefined;
        }
        return false; }; }(this));
    this.addListener(box, 'mouseout',
      function(vt100) { return function(e) {
        return vt100.resetLastSelectedKey(e); }; }(this));
    this.addListener(box, 'mouseover',
      function(vt100) { return function(e) {
        return vt100.resetLastSelectedKey(e); }; }(this));
  
    // Configure SHIFT key behavior
    var style                             = document.createElement('style');
    var id                                = document.createAttribute('id');
    id.nodeValue                          = 'shift_state';
    style.setAttributeNode(id);
    var type                              = document.createAttribute('type');
    type.nodeValue                        = 'text/css';
    style.setAttributeNode(type);
    document.getElementsByTagName('head')[0].appendChild(style);
  
    // Set up key bindings
    this.initializeKeyBindings(box);
  };
  
  VT100.prototype.initializeElements = function(container) {
    // If the necessary objects have not already been defined in the HTML
    // page, create them now.
    if (container) {
      this.container             = container;
    } else if (!(this.container  = document.getElementById('vt100'))) {
      this.container             = document.createElement('div');
      this.container.id          = 'vt100';
      document.body.appendChild(this.container);
    }
  
    if (!this.getChildById(this.container, 'reconnect')   ||
        !this.getChildById(this.container, 'menu')        ||
        !this.getChildById(this.container, 'keyboard')    ||
        !this.getChildById(this.container, 'kbd_button')  ||
        !this.getChildById(this.container, 'kbd_img')     ||
        !this.getChildById(this.container, 'layout')      ||
        !this.getChildById(this.container, 'scrollable')  ||
        !this.getChildById(this.container, 'console')     ||
        !this.getChildById(this.container, 'alt_console') ||
        !this.getChildById(this.container, 'ieprobe')     ||
        !this.getChildById(this.container, 'padding')     ||
        !this.getChildById(this.container, 'cursor')      ||
        !this.getChildById(this.container, 'lineheight')  ||
        !this.getChildById(this.container, 'usercss')     ||
        !this.getChildById(this.container, 'space')       ||
        !this.getChildById(this.container, 'input')       ||
        !this.getChildById(this.container, 'cliphelper')) {
      // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
      // we might get a pointless warning that a suitable plugin is not yet
      // installed. If in doubt, we'd rather just stay silent.
      var embed                  = '';
      try {
        if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
            'undefined') {
          embed                  = typeof suppressAllAudio != 'undefined' &&
                                   suppressAllAudio ? "" :
          '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
                         'id="beep_embed" ' +
                         'src="beep.wav" ' +
                         'autostart="false" ' +
                         'volume="100" ' +
                         'enablejavascript="true" ' +
                         'type="audio/x-wav" ' +
                         'height="16" ' +
                         'width="200" ' +
                         'style="position:absolute;left:-1000px;top:-1000px" />';
        }
      } catch (e) {
      }
  
      this.container.innerHTML   =
                         '<div id="reconnect" style="visibility: hidden">' +
                           '<input type="button" value="Connect" ' +
                                  'onsubmit="return false" />' +
                         '</div>' +
                         '<div id="cursize" style="visibility: hidden">' +
                         '</div>' +
                         '<div id="menu"></div>' +
                         '<div id="keyboard" unselectable="on">' +
                         '</div>' +
                         '<div id="scrollable">' +
                           '<table id="kbd_button">' +
                             '<tr><td width="100%">&nbsp;</td>' +
                             '<td><img id="kbd_img" src="keyboard.png" /></td>' +
                             '<td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr>' +
                           '</table>' +
                           '<pre id="lineheight">&nbsp;</pre>' +
                           '<pre id="console">' +
                             '<pre></pre>' +
                             '<div id="ieprobe"><span>&nbsp;</span></div>' +
                           '</pre>' +
                           '<pre id="alt_console" style="display: none"></pre>' +
                           '<div id="padding"></div>' +
                           '<pre id="cursor">&nbsp;</pre>' +
                         '</div>' +
                         '<div class="hidden">' +
                           '<div id="usercss"></div>' +
                           '<pre><div><span id="space"></span></div></pre>' +
                           '<input type="text" id="input" autocorrect="off" autocapitalize="off" />' +
                           '<input type="text" id="cliphelper" />' +
                           (typeof suppressAllAudio != 'undefined' &&
                            suppressAllAudio ? "" :
                           embed + '<bgsound id="beep_bgsound" loop=1 />') +
                            '<iframe id="layout" src="keyboard.html" />' +
                          '</div>';
    }
  
    // Find the object used for playing the "beep" sound, if any.
    if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
      this.beeper                = undefined;
    } else {
      this.beeper                = this.getChildById(this.container,
                                                     'beep_embed');
      if (!this.beeper || !this.beeper.Play) {
        this.beeper              = this.getChildById(this.container,
                                                     'beep_bgsound');
        if (!this.beeper || typeof this.beeper.src == 'undefined') {
          this.beeper            = undefined;
        }
      }
    }
  
    // Initialize the variables for finding the text console and the
    // cursor.
    this.reconnectBtn            = this.getChildById(this.container,'reconnect');
    this.curSizeBox              = this.getChildById(this.container, 'cursize');
    this.menu                    = this.getChildById(this.container, 'menu');
    this.keyboard                = this.getChildById(this.container, 'keyboard');
    this.keyboardImage           = this.getChildById(this.container, 'kbd_img');
    this.layout                  = this.getChildById(this.container, 'layout');
    this.scrollable              = this.getChildById(this.container,
                                                                   'scrollable');
    this.lineheight              = this.getChildById(this.container,
                                                                   'lineheight');
    this.console                 =
                            [ this.getChildById(this.container, 'console'),
                              this.getChildById(this.container, 'alt_console') ];
    var ieProbe                  = this.getChildById(this.container, 'ieprobe');
    this.padding                 = this.getChildById(this.container, 'padding');
    this.cursor                  = this.getChildById(this.container, 'cursor');
    this.usercss                 = this.getChildById(this.container, 'usercss');
    this.space                   = this.getChildById(this.container, 'space');
    this.input                   = this.getChildById(this.container, 'input');
    this.cliphelper              = this.getChildById(this.container,
                                                                   'cliphelper');
  
    // Add any user selectable style sheets to the menu
    this.initializeUserCSSStyles();
  
    // Remember the dimensions of a standard character glyph. We would
    // expect that we could just check cursor.clientWidth/Height at any time,
    // but it turns out that browsers sometimes invalidate these values
    // (e.g. while displaying a print preview screen).
    this.cursorWidth             = this.cursor.clientWidth;
    this.cursorHeight            = this.lineheight.clientHeight;
  
    // IE has a slightly different boxing model, that we need to compensate for
    this.isIE                    = ieProbe.offsetTop > 1;
    ieProbe                      = undefined;
    this.console.innerHTML       = '';
  
    // Determine if the terminal window is positioned at the beginning of the
    // page, or if it is embedded somewhere else in the page. For full-screen
    // terminals, automatically resize whenever the browser window changes.
    var marginTop                = parseInt(this.getCurrentComputedStyle(
                                            document.body, 'marginTop'));
    var marginLeft               = parseInt(this.getCurrentComputedStyle(
                                            document.body, 'marginLeft'));
    var marginRight              = parseInt(this.getCurrentComputedStyle(
                                            document.body, 'marginRight'));
    var x                        = this.container.offsetLeft;
    var y                        = this.container.offsetTop;
    for (var parent = this.container; parent = parent.offsetParent; ) {
      x                         += parent.offsetLeft;
      y                         += parent.offsetTop;
    }
    // When zoom functionality is used, some browsers report container offsetWidth
    // wrong by one pixel. This is the reason why widthDiff can be 0 or -1 pixel,
    // and in this case variable isEmbedded is not set to true.
    var widthDiff                = ((window.innerWidth ||
                                     document.documentElement.clientWidth ||
                                     document.body.clientWidth) - marginRight)
                                   - (x + this.container.offsetWidth);
    this.isEmbedded              = marginTop != y ||
                                   marginLeft != x ||
                                   (widthDiff != 0 && widthDiff != -1);
    if (!this.isEmbedded) {
      // Some browsers generate resize events when the terminal is first
      // shown. Disable showing the size indicator until a little bit after
      // the terminal has been rendered the first time.
      this.indicateSize          = false;
      setTimeout(function(vt100) {
        return function() {
          vt100.indicateSize     = true;
        };
      }(this), 100);
      this.addListener(window, 'resize',
                       function(vt100) {
                         return function() {
                           vt100.hideContextMenu();
                           vt100.resizer();
                           vt100.showCurrentSize();
                          }
                        }(this));
  
      // Hide extra scrollbars attached to window
      document.body.style.margin = '0px';
      try { document.body.style.overflow ='hidden'; } catch (e) { }
      try { document.body.oncontextmenu = function() {return false;};} catch(e){}
    }
  
    // Set up onscreen soft keyboard
    this.initializeKeyboardButton();
  
    // Hide context menu
    this.hideContextMenu();
  
    // Add listener to reconnect button
    this.addListener(this.reconnectBtn.firstChild, 'click',
                     function(vt100) {
                       return function() {
                         var rc = vt100.reconnect();
                         vt100.input.focus();
                         return rc;
                       }
                     }(this));
  
    // Add input listeners
    this.addListener(this.input, 'blur',
                     function(vt100) {
                       return function() { vt100.blurCursor(); } }(this));
    this.addListener(this.input, 'focus',
                     function(vt100) {
                       return function() { vt100.focusCursor(); } }(this));
    this.addListener(this.input, 'keydown',
                     function(vt100) {
                       return function(e) {
                         if (!e) e = window.event;
                         return vt100.keyDown(e); } }(this));
    this.addListener(this.input, 'keypress',
                     function(vt100) {
                       return function(e) {
                         if (!e) e = window.event;
                         return vt100.keyPressed(e); } }(this));
    this.addListener(this.input, 'keyup',
                     function(vt100) {
                       return function(e) {
                         if (!e) e = window.event;
                         return vt100.keyUp(e); } }(this));
  
    // Attach listeners that move the focus to the <input> field. This way we
    // can make sure that we can receive keyboard input.
    var mouseEvent               = function(vt100, type) {
      return function(e) {
        if (!e) e = window.event;
        return vt100.mouseEvent(e, type);
      };
    };
    this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
    this.addListener(this.scrollable,'mouseup',  mouseEvent(this, 1 /* MOUSE_UP */));
    this.addListener(this.scrollable,'click',    mouseEvent(this, 2 /* MOUSE_CLICK */));
  
    // Initialize the blank terminal window.
    this.currentScreen           = 0;
    this.cursorX                 = 0;
    this.cursorY                 = 0;
    this.numScrollbackLines      = 0;
    this.top                     = 0;
    this.bottom                  = 0x7FFFFFFF;
    this.scale                   = 1.0;
    this.resizer();
    this.focusCursor();
    this.input.focus();
  };
  
  VT100.prototype.getChildById = function(parent, id) {
    var nodeList = parent.all || parent.getElementsByTagName('*');
    if (typeof nodeList.namedItem == 'undefined') {
      for (var i = 0; i < nodeList.length; i++) {
        if (nodeList[i].id == id) {
          return nodeList[i];
        }
      }
      return null;
    } else {
      var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
      return elem ? elem[0] || elem : null;
    }
  };
  
  VT100.prototype.getCurrentComputedStyle = function(elem, style) {
    if (typeof elem.currentStyle != 'undefined') {
      return elem.currentStyle[style];
    } else {
      return document.defaultView.getComputedStyle(elem, null)[style];
    }
  };
  
  VT100.prototype.reconnect = function() {
    return false;
  };
  
  VT100.prototype.showReconnect = function(state) {
    if (state) {
      this.hideSoftKeyboard();
      this.reconnectBtn.style.visibility = '';
    } else {
      this.reconnectBtn.style.visibility = 'hidden';
    }
  };
  
  VT100.prototype.repairElements = function(console) {
    for (var line = console.firstChild; line; line = line.nextSibling) {
      if (!line.clientHeight) {
        var newLine = document.createElement(line.tagName);
        newLine.style.cssText       = line.style.cssText;
        newLine.className           = line.className;
        if (line.tagName == 'DIV') {
          for (var span = line.firstChild; span; span = span.nextSibling) {
            var newSpan             = document.createElement(span.tagName);
            newSpan.style.cssText   = span.style.cssText;
            newSpan.className       = span.className;
            this.setTextContent(newSpan, this.getTextContent(span));
            newLine.appendChild(newSpan);
          }
        } else {
          this.setTextContent(newLine, this.getTextContent(line));
        }
        line.parentNode.replaceChild(newLine, line);
        line                        = newLine;
      }
    }
  };
  
  VT100.prototype.resized = function(w, h) {
  };
  
  VT100.prototype.resizer = function() {
    // Hide onscreen soft keyboard
    this.hideSoftKeyboard();
  
    // The cursor can get corrupted if the print-preview is displayed in Firefox.
    // Recreating it, will repair it.
    var newCursor                = document.createElement('pre');
    this.setTextContent(newCursor, ' ');
    newCursor.id                 = 'cursor';
    newCursor.style.cssText      = this.cursor.style.cssText;
    this.cursor.parentNode.insertBefore(newCursor, this.cursor);
    if (!newCursor.clientHeight) {
      // Things are broken right now. This is probably because we are
      // displaying the print-preview. Just don't change any of our settings
      // until the print dialog is closed again.
      newCursor.parentNode.removeChild(newCursor);
      return;
    } else {
      // Swap the old broken cursor for the newly created one.
      this.cursor.parentNode.removeChild(this.cursor);
      this.cursor                = newCursor;
    }
  
    // Really horrible things happen if the contents of the terminal changes
    // while the print-preview is showing. We get HTML elements that show up
    // in the DOM, but that do not take up any space. Find these elements and
    // try to fix them.
    this.repairElements(this.console[0]);
    this.repairElements(this.console[1]);
  
    // Lock the cursor size to the size of a normal character. This helps with
    // characters that are taller/shorter than normal. Unfortunately, we will
    // still get confused if somebody enters a character that is wider/narrower
    // than normal. This can happen if the browser tries to substitute a
    // characters from a different font.
    if (this.cursorWidth > 0) {
      this.cursor.style.width    = this.cursorWidth  + 'px';
    }
    if (this.cursorHeight > 0) {
      this.cursor.style.height   = this.cursorHeight + 'px';
    }
  
    // Adjust height for one pixel padding of the #vt100 element.
    // The latter is necessary to properly display the inactive cursor.
    var console                  = this.console[this.currentScreen];
    var height                   = (this.isEmbedded ? this.container.clientHeight
                                    : (window.innerHeight ||
                                       document.documentElement.clientHeight ||
                                       document.body.clientHeight))-1;
  
    // Prevent ever growing consoles on iPad.
    if (navigator.userAgent.match(/iPad/i) != null) {
      height                    -= 1;
    }
  
    var partial                  = height % this.cursorHeight;
    this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
    this.padding.style.height    = (partial > 0 ? partial : 0) + 'px';
    var oldTerminalHeight        = this.terminalHeight;
    this.updateWidth();
    this.updateHeight();
  
    // Clip the cursor to the visible screen.
    var cx                       = this.cursorX;
    var cy                       = this.cursorY + this.numScrollbackLines;
  
    // The alternate screen never keeps a scroll back buffer.
    this.updateNumScrollbackLines();
    while (this.currentScreen && this.numScrollbackLines > 0) {
      console.removeChild(console.firstChild);
      this.numScrollbackLines--;
    }
    cy                          -= this.numScrollbackLines;
    if (cx < 0) {
      cx                         = 0;
    } else if (cx > this.terminalWidth) {
      cx                         = this.terminalWidth - 1;
      if (cx < 0) {
        cx                       = 0;
      }
    }
    if (cy < 0) {
      cy                         = 0;
    } else if (cy > this.terminalHeight) {
      cy                         = this.terminalHeight - 1;
      if (cy < 0) {
        cy                       = 0;
      }
    }
  
    // Clip the scroll region to the visible screen.
    if (this.bottom > this.terminalHeight ||
        this.bottom == oldTerminalHeight) {
      this.bottom                = this.terminalHeight;
    }
    if (this.top >= this.bottom) {
      this.top                   = this.bottom-1;
      if (this.top < 0) {
        this.top                 = 0;
      }
    }
  
    // Truncate lines, if necessary. Explicitly reposition cursor (this is
    // particularly important after changing the screen number), and reset
    // the scroll region to the default.
    this.truncateLines(this.terminalWidth);
    this.putString(cx, cy, '', undefined);
    this.scrollable.scrollTop    = this.numScrollbackLines *
                                   this.cursorHeight + 1;
  
    // Update classNames for lines in the scrollback buffer
    var line                     = console.firstChild;
    for (var i = 0; i < this.numScrollbackLines; i++) {
      line.className             = 'scrollback';
      line                       = line.nextSibling;
    }
    while (line) {
      line.className             = '';
      line                       = line.nextSibling;
    }
  
    // Reposition the reconnect button
    this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
                                    this.scale -
                                    this.reconnectBtn.clientWidth)/2 + 'px';
    this.reconnectBtn.style.top  = (this.terminalHeight*this.cursorHeight-
                                    this.reconnectBtn.clientHeight)/2 + 'px';
  
    // Send notification that the window size has been changed
    this.resized(this.terminalWidth, this.terminalHeight);
  };
  
  VT100.prototype.showCurrentSize = function() {
    if (!this.indicateSize) {
      return;
    }
    this.curSizeBox.innerHTML             = '' + this.terminalWidth + 'x' +
                                                 this.terminalHeight;
    this.curSizeBox.style.left            =
                                        (this.terminalWidth*this.cursorWidth/
                                         this.scale -
                                         this.curSizeBox.clientWidth)/2 + 'px';
    this.curSizeBox.style.top             =
                                        (this.terminalHeight*this.cursorHeight -
                                         this.curSizeBox.clientHeight)/2 + 'px';
    this.curSizeBox.style.visibility      = '';
    if (this.curSizeTimeout) {
      clearTimeout(this.curSizeTimeout);
    }
  
    // Only show the terminal size for a short amount of time after resizing.
    // Then hide this information, again. Some browsers generate resize events
    // throughout the entire resize operation. This is nice, and we will show
    // the terminal size while the user is dragging the window borders.
    // Other browsers only generate a single event when the user releases the
    // mouse. In those cases, we can only show the terminal size once at the
    // end of the resize operation.
    this.curSizeTimeout                   = setTimeout(function(vt100) {
      return function() {
        vt100.curSizeTimeout              = null;
        vt100.curSizeBox.style.visibility = 'hidden';
      };
    }(this), 1000);
  };
  
  VT100.prototype.selection = function() {
    try {
      return '' + (window.getSelection && window.getSelection() ||
                   document.selection && document.selection.type == 'Text' &&
                   document.selection.createRange().text || '');
    } catch (e) {
    }
    return '';
  };
  
  VT100.prototype.cancelEvent = function(event) {
    try {
      // For non-IE browsers
      event.stopPropagation();
      event.preventDefault();
    } catch (e) {
    }
    try {
      // For IE
      event.cancelBubble = true;
      event.returnValue  = false;
      event.button       = 0;
      event.keyCode      = 0;
    } catch (e) {
    }
    return false;
  };
  
  VT100.prototype.mousePosition = function(event) {
    var offsetX      = this.container.offsetLeft;
    var offsetY      = this.container.offsetTop;
    for (var e = this.container; e = e.offsetParent; ) {
      offsetX       += e.offsetLeft;
      offsetY       += e.offsetTop;
    }
    return [ event.clientX - offsetX,
             event.clientY - offsetY ];
  };
  
  VT100.prototype.mouseEvent = function(event, type) {
    // If any text is currently selected, do not move the focus as that would
    // invalidate the selection.
    var selection    = this.selection();
    if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
      this.input.setAttribute('style', 'position:fixed; top:' + event.y + 'px;opacity:0;z-index;-10');
      this.input.focus();
    }
  
    // Compute mouse position in characters.
    var position     = this.mousePosition(event);
    var x            = Math.floor(position[0] / this.cursorWidth);
    var y            = Math.floor((position[1] + this.scrollable.scrollTop) /
                                  this.cursorHeight) - this.numScrollbackLines;
    var inside       = true;
    if (x >= this.terminalWidth) {
      x              = this.terminalWidth - 1;
      inside         = false;
    }
    if (x < 0) {
      x              = 0;
      inside         = false;
    }
    if (y >= this.terminalHeight) {
      y              = this.terminalHeight - 1;
      inside         = false;
    }
    if (y < 0) {
      y              = 0;
      inside         = false;
    }
  
    // Compute button number and modifier keys.
    var button       = type != 0 /* MOUSE_DOWN */ ? 3 :
                       typeof event.pageX != 'undefined' ? event.button :
                       [ undefined, 0, 2, 0, 1, 0, 1, 0  ][event.button];
    if (button != undefined) {
      if (event.shiftKey) {
        button      |= 0x04;
      }
      if (event.altKey || event.metaKey) {
        button      |= 0x08;
      }
      if (event.ctrlKey) {
        button      |= 0x10;
      }
    }
  
    // Report mouse events if they happen inside of the current screen and
    // with the SHIFT key unpressed. Both of these restrictions do not apply
    // for button releases, as we always want to report those.
    if (this.mouseReporting && !selection.length &&
        (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
      if (inside || type != 0 /* MOUSE_DOWN */) {
        if (button != undefined) {
          var report = '\u001B[M' + String.fromCharCode(button + 32) +
                                    String.fromCharCode(x      + 33) +
                                    String.fromCharCode(y      + 33);
          if (type != 2 /* MOUSE_CLICK */) {
            this.keysPressed(report);
          }
  
          // If we reported the event, stop propagating it (not sure, if this
          // actually works on most browsers; blocking the global "oncontextmenu"
          // even is still necessary).
          return this.cancelEvent(event);
        }
      }
    }
  
    // Bring up context menu.
    if (button == 2 && !event.shiftKey) {
      if (type == 0 /* MOUSE_DOWN */) {
        this.showContextMenu(position[0], position[1]);
      }
      return this.cancelEvent(event);
    }
  
    if (this.mouseReporting) {
      try {
        event.shiftKey         = false;
      } catch (e) {
      }
    }
  
    return true;
  };
  
  VT100.prototype.replaceChar = function(s, ch, repl) {
    for (var i = -1;;) {
      i = s.indexOf(ch, i + 1);
      if (i < 0) {
        break;
      }
      s = s.substr(0, i) + repl + s.substr(i + 1);
    }
    return s;
  };
  
  VT100.prototype.htmlEscape = function(s) {
    return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
                  s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;'), ' ', '\u00A0');
  };
  
  VT100.prototype.getTextContent = function(elem) {
    return elem.textContent ||
           (typeof elem.textContent == 'undefined' ? elem.innerText : '');
  };
  
  VT100.prototype.setTextContentRaw = function(elem, s) {
    // Updating the content of an element is an expensive operation. It actually
    // pays off to first check whether the element is still unchanged.
    if (typeof elem.textContent == 'undefined') {
      if (elem.innerText != s) {
        try {
          elem.innerText = s;
        } catch (e) {
          // Very old versions of IE do not allow setting innerText. Instead,
          // remove all children, by setting innerHTML and then set the text
          // using DOM methods.
          elem.innerHTML = '';
          elem.appendChild(document.createTextNode(
                                            this.replaceChar(s, ' ', '\u00A0')));
        }
      }
    } else {
      if (elem.textContent != s) {
        elem.textContent = s;
      }
    }
  };
  
  VT100.prototype.setTextContent = function(elem, s) {
    // Check if we find any URLs in the text. If so, automatically convert them
    // to links.
    if (this.urlRE && this.urlRE.test(s)) {
      var inner          = '';
      for (;;) {
        var consumed = 0;
        if (RegExp.leftContext != null) {
          inner         += this.htmlEscape(RegExp.leftContext);
          consumed      += RegExp.leftContext.length;
        }
        var url          = this.htmlEscape(RegExp.lastMatch);
        var fullUrl      = url;
  
        // If no protocol was specified, try to guess a reasonable one.
        if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
            url.indexOf('ftp://')  < 0 && url.indexOf('mailto:')  < 0) {
          var slash      = url.indexOf('/');
          var at         = url.indexOf('@');
          var question   = url.indexOf('?');
          if (at > 0 &&
              (at < question || question < 0) &&
              (slash < 0 || (question > 0 && slash > question))) {
            fullUrl      = 'mailto:' + url;
          } else {
            fullUrl      = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
                            url;
          }
        }
  
        inner           += '<a target="vt100Link" href="' + fullUrl +
                           '">' + url + '</a>';
        consumed        += RegExp.lastMatch.length;
        s                = s.substr(consumed);
        if (!this.urlRE.test(s)) {
          if (RegExp.rightContext != null) {
            inner       += this.htmlEscape(RegExp.rightContext);
          }
          break;
        }
      }
      elem.innerHTML     = inner;
      return;
    }
  
    this.setTextContentRaw(elem, s);
  };
  
  VT100.prototype.insertBlankLine = function(y, color, style) {
    // Insert a blank line a position y. This method ignores the scrollback
    // buffer. The caller has to add the length of the scrollback buffer to
    // the position, if necessary.
    // If the position is larger than the number of current lines, this
    // method just adds a new line right after the last existing one. It does
    // not add any missing lines in between. It is the caller's responsibility
    // to do so.
    if (!color) {
      color                = 'ansiDef bgAnsiDef';
    }
    if (!style) {
      style                = '';
    }
    var line;
    if (color != 'ansiDef bgAnsiDef' && !style) {
      line                 = document.createElement('pre');
      this.setTextContent(line, '\n');
    } else {
      line                 = document.createElement('div');
      var span             = document.createElement('span');
      span.style.cssText   = style;
      span.className       = color;
      this.setTextContent(span, this.spaces(this.terminalWidth));
      line.appendChild(span);
    }
    line.style.height      = this.cursorHeight + 'px';
    var console            = this.console[this.currentScreen];
    if (console.childNodes.length > y) {
      console.insertBefore(line, console.childNodes[y]);
    } else {
      console.appendChild(line);
    }
  };
  
  VT100.prototype.updateWidth = function() {
    //  if the cursorWidth is zero, something is wrong. Try to get it some other way.
    if (this.cursorWidth <= 0) {
      if (this.cursor.clientWidth <= 0) {
        // Rats, this.cursor.clientWidth is zero too. Best guess?
        this.terminalWidth = 80;
      } else {
        // update the size.
        this.cursorWidth = this.cursor.clientWidth;
        this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/this.cursorWidth*this.scale);
      }
    } else {
      this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/this.cursorWidth*this.scale);
    }
  
    return this.terminalWidth;
  };
  
  VT100.prototype.updateHeight = function() {
    // We want to be able to display either a terminal window that fills the
    // entire browser window, or a terminal window that is contained in a
    // <div> which is embededded somewhere in the web page.
    if (this.isEmbedded) {
      // Embedded terminal. Use size of the containing <div> (id="vt100").
      this.terminalHeight = Math.floor((this.container.clientHeight-1) /
                                       this.cursorHeight);
    } else {
      // Use the full browser window.
      this.terminalHeight = Math.floor(((window.innerHeight ||
                                         document.documentElement.clientHeight ||
                                         document.body.clientHeight)-1)/
                                       this.cursorHeight);
    }
    return this.terminalHeight;
  };
  
  VT100.prototype.updateNumScrollbackLines = function() {
    var scrollback          = Math.floor(
                                  this.console[this.currentScreen].offsetHeight /
                                  this.cursorHeight) -
                              this.terminalHeight;
    this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
    return this.numScrollbackLines;
  };
  
  VT100.prototype.truncateLines = function(width) {
    if (width < 0) {
      width             = 0;
    }
    for (var line = this.console[this.currentScreen].firstChild; line;
         line = line.nextSibling) {
      if (line.tagName == 'DIV') {
        var x           = 0;
  
        // Traverse current line and truncate it once we saw "width" characters
        for (var span = line.firstChild; span;
             span = span.nextSibling) {
          var s         = this.getTextContent(span);
          var l         = s.length;
          if (x + l > width) {
            this.setTextContent(span, s.substr(0, width - x));
            while (span.nextSibling) {
              line.removeChild(line.lastChild);
            }
            break;
          }
          x            += l;
        }
        // Prune white space from the end of the current line
        var span       = line.lastChild;
        while (span &&
               span.className == 'ansiDef bgAnsiDef' &&
               !span.style.cssText.length) {
          // Scan backwards looking for first non-space character
          var s         = this.getTextContent(span);
          for (var i = s.length; i--; ) {
            if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
              if (i+1 != s.length) {
                this.setTextContent(s.substr(0, i+1));
              }
              span      = null;
              break;
            }
          }
          if (span) {
            var sibling = span;
            span        = span.previousSibling;
            if (span) {
              // Remove blank <span>'s from end of line
              line.removeChild(sibling);
            } else {
              // Remove entire line (i.e. <div>), if empty
              var blank = document.createElement('pre');
              blank.style.height = this.cursorHeight + 'px';
              this.setTextContent(blank, '\n');
              line.parentNode.replaceChild(blank, line);
            }
          }
        }
      }
    }
  };
  
  VT100.prototype.putString = function(x, y, text, color, style) {
    if (!color) {
      color                           = 'ansiDef bgAnsiDef';
    }
    if (!style) {
      style                           = '';
    }
    var yIdx                          = y + this.numScrollbackLines;
    var line;
    var sibling;
    var s;
    var span;
    var xPos                          = 0;
    var console                       = this.console[this.currentScreen];
    if (!text.length && (yIdx >= console.childNodes.length ||
                         console.childNodes[yIdx].tagName != 'DIV')) {
      // Positioning cursor to a blank location
      span                            = null;
    } else {
      // Create missing blank lines at end of page
      while (console.childNodes.length <= yIdx) {
        // In order to simplify lookups, we want to make sure that each line
        // is represented by exactly one element (and possibly a whole bunch of
        // children).
        // For non-blank lines, we can create a <div> containing one or more
        // <span>s. For blank lines, this fails as browsers tend to optimize them
        // away. But fortunately, a <pre> tag containing a newline character
        // appears to work for all browsers (a &nbsp; would also work, but then
        // copying from the browser window would insert superfluous spaces into
        // the clipboard).
        this.insertBlankLine(yIdx);
      }
      line                            = console.childNodes[yIdx];
  
      // If necessary, promote blank '\n' line to a <div> tag
      if (line.tagName != 'DIV') {
        var div                       = document.createElement('div');
        div.style.height              = this.cursorHeight + 'px';
        div.innerHTML                 = '<span></span>';
        console.replaceChild(div, line);
        line                          = div;
      }
  
      // Scan through list of <span>'s until we find the one where our text
      // starts
      span                            = line.firstChild;
      var len;
      while (span.nextSibling && xPos < x) {
        len                           = this.getTextContent(span).length;
        if (xPos + len > x) {
          break;
        }
        xPos                         += len;
        span                          = span.nextSibling;
      }
  
      if (text.length) {
        // If current <span> is not long enough, pad with spaces or add new
        // span
        s                             = this.getTextContent(span);
        var oldColor                  = span.className;
        var oldStyle                  = span.style.cssText;
        if (xPos + s.length < x) {
          if (oldColor != 'ansiDef bgAnsiDef' || oldStyle != '') {
            span                      = document.createElement('span');
            line.appendChild(span);
            span.className            = 'ansiDef bgAnsiDef';
            span.style.cssText        = '';
            oldColor                  = 'ansiDef bgAnsiDef';
            oldStyle                  = '';
            xPos                     += s.length;
            s                         = '';
          }
          do {
            s                        += ' ';
          } while (xPos + s.length < x);
        }
  
        // If styles do not match, create a new <span>
        var del                       = text.length - s.length + x - xPos;
        if (oldColor != color ||
            (oldStyle != style && (oldStyle || style))) {
          if (xPos == x) {
            // Replacing text at beginning of existing <span>
            if (text.length >= s.length) {
              // New text is equal or longer than existing text
              s                       = text;
            } else {
              // Insert new <span> before the current one, then remove leading
              // part of existing <span>, adjust style of new <span>, and finally
              // set its contents
              sibling                 = document.createElement('span');
              line.insertBefore(sibling, span);
              this.setTextContent(span, s.substr(text.length));
              span                    = sibling;
              s                       = text;
            }
          } else {
            // Replacing text some way into the existing <span>
            var remainder             = s.substr(x + text.length - xPos);
            this.setTextContent(span, s.substr(0, x - xPos));
            xPos                      = x;
            sibling                   = document.createElement('span');
            if (span.nextSibling) {
              line.insertBefore(sibling, span.nextSibling);
              span                    = sibling;
              if (remainder.length) {
                sibling               = document.createElement('span');
                sibling.className     = oldColor;
                sibling.style.cssText = oldStyle;
                this.setTextContent(sibling, remainder);
                line.insertBefore(sibling, span.nextSibling);
              }
            } else {
              line.appendChild(sibling);
              span                    = sibling;
              if (remainder.length) {
                sibling               = document.createElement('span');
                sibling.className     = oldColor;
                sibling.style.cssText = oldStyle;
                this.setTextContent(sibling, remainder);
                line.appendChild(sibling);
              }
            }
            s                         = text;
          }
          span.className              = color;
          span.style.cssText          = style;
        } else {
          // Overwrite (partial) <span> with new text
          s                           = s.substr(0, x - xPos) +
            text +
            s.substr(x + text.length - xPos);
        }
        this.setTextContent(span, s);
  
  
        // Delete all subsequent <span>'s that have just been overwritten
        sibling                       = span.nextSibling;
        while (del > 0 && sibling) {
          s                           = this.getTextContent(sibling);
          len                         = s.length;
          if (len <= del) {
            line.removeChild(sibling);
            del                      -= len;
            sibling                   = span.nextSibling;
          } else {
            this.setTextContent(sibling, s.substr(del));
            break;
          }
        }
  
        // Merge <span> with next sibling, if styles are identical
        if (sibling && span.className == sibling.className &&
            span.style.cssText == sibling.style.cssText) {
          this.setTextContent(span,
                              this.getTextContent(span) +
                              this.getTextContent(sibling));
          line.removeChild(sibling);
        }
      }
    }
  
    // Position cursor
    this.cursorX                      = x + text.length;
    if (this.cursorX >= this.terminalWidth) {
      this.cursorX                    = this.terminalWidth - 1;
      if (this.cursorX < 0) {
        this.cursorX                  = 0;
      }
    }
    var pixelX                        = -1;
    var pixelY                        = -1;
    if (!this.cursor.style.visibility) {
      var idx                         = this.cursorX - xPos;
      if (span) {
        // If we are in a non-empty line, take the cursor Y position from the
        // other elements in this line. If dealing with broken, non-proportional
        // fonts, this is likely to yield better results.
        pixelY                        = span.offsetTop +
                                        span.offsetParent.offsetTop;
        s                             = this.getTextContent(span);
        var nxtIdx                    = idx - s.length;
        if (nxtIdx < 0) {
          this.setTextContent(this.cursor, s.charAt(idx));
          pixelX                      = span.offsetLeft +
                                        idx*span.offsetWidth / s.length;
        } else {
          if (nxtIdx == 0) {
            pixelX                    = span.offsetLeft + span.offsetWidth;
          }
          if (span.nextSibling) {
            s                         = this.getTextContent(span.nextSibling);
            this.setTextContent(this.cursor, s.charAt(nxtIdx));
            if (pixelX < 0) {
              pixelX                  = span.nextSibling.offsetLeft +
                                        nxtIdx*span.offsetWidth / s.length;
            }
          } else {
            this.setTextContent(this.cursor, ' ');
          }
        }
      } else {
        this.setTextContent(this.cursor, ' ');
      }
    }
    if (pixelX >= 0) {
      this.cursor.style.left          = (pixelX + (this.isIE ? 1 : 0))/
                                        this.scale + 'px';
    } else {
      this.setTextContent(this.space, this.spaces(this.cursorX));
      this.cursor.style.left          = (this.space.offsetWidth +
                                         console.offsetLeft)/this.scale + 'px';
    }
    this.cursorY                      = yIdx - this.numScrollbackLines;
    if (pixelY >= 0) {
      this.cursor.style.top           = pixelY + 'px';
    } else {
      this.cursor.style.top           = yIdx*this.cursorHeight +
                                        console.offsetTop + 'px';
    }
  
    if (text.length) {
      // Merge <span> with previous sibling, if styles are identical
      if ((sibling = span.previousSibling) &&
          span.className == sibling.className &&
          span.style.cssText == sibling.style.cssText) {
        this.setTextContent(span,
                            this.getTextContent(sibling) +
                            this.getTextContent(span));
        line.removeChild(sibling);
      }
  
      // Prune white space from the end of the current line
      span                            = line.lastChild;
      while (span &&
             span.className == 'ansiDef bgAnsiDef' &&
             !span.style.cssText.length) {
        // Scan backwards looking for first non-space character
        s                             = this.getTextContent(span);
        for (var i = s.length; i--; ) {
          if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
            if (i+1 != s.length) {
              this.setTextContent(s.substr(0, i+1));
            }
            span                      = null;
            break;
          }
        }
        if (span) {
          sibling                     = span;
          span                        = span.previousSibling;
          if (span) {
            // Remove blank <span>'s from end of line
            line.removeChild(sibling);
          } else {
            // Remove entire line (i.e. <div>), if empty
            var blank                 = document.createElement('pre');
            blank.style.height        = this.cursorHeight + 'px';
            this.setTextContent(blank, '\n');
            line.parentNode.replaceChild(blank, line);
          }
        }
      }
    }
  };
  
  VT100.prototype.gotoXY = function(x, y) {
    if (x >= this.terminalWidth) {
      x           = this.terminalWidth - 1;
    }
    if (x < 0) {
      x           = 0;
    }
    var minY, maxY;
    if (this.offsetMode) {
      minY        = this.top;
      maxY        = this.bottom;
    } else {
      minY        = 0;
      maxY        = this.terminalHeight;
    }
    if (y >= maxY) {
      y           = maxY - 1;
    }
    if (y < minY) {
      y           = minY;
    }
    this.putString(x, y, '', undefined);
    this.needWrap = false;
  };
  
  VT100.prototype.gotoXaY = function(x, y) {
    this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
  };
  
  VT100.prototype.refreshInvertedState = function() {
    if (this.isInverted) {
      this.scrollable.className += ' inverted';
    } else {
      this.scrollable.className = this.scrollable.className.
                                                       replace(/ *inverted/, '');
    }
  };
  
  VT100.prototype.enableAlternateScreen = function(state) {
    // Don't do anything, if we are already on the desired screen
    if ((state ? 1 : 0) == this.currentScreen) {
      // Calling the resizer is not actually necessary. But it is a good way
      // of resetting state that might have gotten corrupted.
      this.resizer();
      return;
    }
  
    // We save the full state of the normal screen, when we switch away from it.
    // But for the alternate screen, no saving is necessary. We always reset
    // it when we switch to it.
    if (state) {
      this.saveCursor();
    }
  
    // Display new screen, and initialize state (the resizer does that for us).
    this.currentScreen                                 = state ? 1 : 0;
    this.console[1-this.currentScreen].style.display   = 'none';
    this.console[this.currentScreen].style.display     = '';
  
    // Select appropriate character pitch.
    var transform                                      = this.getTransformName();
    if (transform) {
      if (state) {
        // Upon enabling the alternate screen, we switch to 80 column mode. But
        // upon returning to the regular screen, we restore the mode that was
        // in effect previously.
        this.console[1].style[transform]               = '';
      }
      var style                                        =
                               this.console[this.currentScreen].style[transform];
      this.cursor.style[transform]                     = style;
      this.space.style[transform]                      = style;
      this.scale                                       = style == '' ? 1.0:1.65;
      if (transform == 'filter') {
         this.console[this.currentScreen].style.width  = style == '' ? '165%':'';
      }
    }
    this.resizer();
  
    // If we switched to the alternate screen, reset it completely. Otherwise,
    // restore the saved state.
    if (state) {
      this.gotoXY(0, 0);
      this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
    } else {
      this.restoreCursor();
    }
  };
  
  VT100.prototype.hideCursor = function() {
    var hidden = this.cursor.style.visibility == 'hidden';
    if (!hidden) {
      this.cursor.style.visibility = 'hidden';
      return true;
    }
    return false;
  };
  
  VT100.prototype.showCursor = function(x, y) {
    if (this.cursor.style.visibility) {
      this.cursor.style.visibility = '';
      this.putString(x == undefined ? this.cursorX : x,
                     y == undefined ? this.cursorY : y,
                     '', undefined);
      return true;
    }
    return false;
  };
  
  VT100.prototype.scrollBack = function() {
    var i                     = this.scrollable.scrollTop -
                                this.scrollable.clientHeight;
    this.scrollable.scrollTop = i < 0 ? 0 : i;
  };
  
  VT100.prototype.scrollFore = function() {
    var i                     = this.scrollable.scrollTop +
                                this.scrollable.clientHeight;
    this.scrollable.scrollTop = i > this.numScrollbackLines *
                                    this.cursorHeight + 1
                                ? this.numScrollbackLines *
                                  this.cursorHeight + 1
                                : i;
  };
  
  VT100.prototype.spaces = function(i) {
    var s = '';
    while (i-- > 0) {
      s += ' ';
    }
    return s;
  };
  
  VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
    w         += x;
    if (x < 0) {
      x        = 0;
    }
    if (w > this.terminalWidth) {
      w        = this.terminalWidth;
    }
    if ((w    -= x) <= 0) {
      return;
    }
    h         += y;
    if (y < 0) {
      y        = 0;
    }
    if (h > this.terminalHeight) {
      h        = this.terminalHeight;
    }
    if ((h    -= y) <= 0) {
      return;
    }
  
    // Special case the situation where we clear the entire screen, and we do
    // not have a scrollback buffer. In that case, we should just remove all
    // child nodes.
    if (!this.numScrollbackLines &&
        w == this.terminalWidth && h == this.terminalHeight &&
        (color == undefined || color == 'ansiDef bgAnsiDef') && !style) {
      var console = this.console[this.currentScreen];
      while (console.lastChild) {
        console.removeChild(console.lastChild);
      }
      this.putString(this.cursorX, this.cursorY, '', undefined);
    } else {
      var hidden = this.hideCursor();
      var cx     = this.cursorX;
      var cy     = this.cursorY;
      var s      = this.spaces(w);
      for (var i = y+h; i-- > y; ) {
        this.putString(x, i, s, color, style);
      }
      hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
    }
  };
  
  VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
    var text                            = [ ];
    var className                       = [ ];
    var style                           = [ ];
    var console                         = this.console[this.currentScreen];
    if (sY >= console.childNodes.length) {
      text[0]                           = this.spaces(w);
      className[0]                      = undefined;
      style[0]                          = undefined;
    } else {
      var line = console.childNodes[sY];
      if (line.tagName != 'DIV' || !line.childNodes.length) {
        text[0]                         = this.spaces(w);
        className[0]                    = undefined;
        style[0]                        = undefined;
      } else {
        var x                           = 0;
        for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
          var s                         = this.getTextContent(span);
          var len                       = s.length;
          if (x + len > sX) {
            var o                       = sX > x ? sX - x : 0;
            text[text.length]           = s.substr(o, w);
            className[className.length] = span.className;
            style[style.length]         = span.style.cssText;
            w                          -= len - o;
          }
          x                            += len;
        }
        if (w > 0) {
          text[text.length]             = this.spaces(w);
          className[className.length]   = undefined;
          style[style.length]           = undefined;
        }
      }
    }
    var hidden                          = this.hideCursor();
    var cx                              = this.cursorX;
    var cy                              = this.cursorY;
    for (var i = 0; i < text.length; i++) {
      var color;
      if (className[i]) {
        color                           = className[i];
      } else {
        color                           = 'ansiDef bgAnsiDef';
      }
      this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
      dX                               += text[i].length;
    }
    hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
  };
  
  VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
                                          color, style) {
    var left             = incX < 0 ? -incX : 0;
    var right            = incX > 0 ?  incX : 0;
    var up               = incY < 0 ? -incY : 0;
    var down             = incY > 0 ?  incY : 0;
  
    // Clip region against terminal size
    var dontScroll       = null;
    w                   += x;
    if (x < left) {
      x                  = left;
    }
    if (w > this.terminalWidth - right) {
      w                  = this.terminalWidth - right;
    }
    if ((w              -= x) <= 0) {
      dontScroll         = 1;
    }
    h                   += y;
    if (y < up) {
      y                  = up;
    }
    if (h > this.terminalHeight - down) {
      h                  = this.terminalHeight - down;
    }
    if ((h              -= y) < 0) {
      dontScroll         = 1;
    }
    if (!dontScroll) {
      if (style && style.indexOf('underline')) {
        // Different terminal emulators disagree on the attributes that
        // are used for scrolling. The consensus seems to be, never to
        // fill with underlined spaces. N.B. this is different from the
        // cases when the user blanks a region. User-initiated blanking
        // always fills with all of the current attributes.
        style            = style.replace(/text-decoration:underline;/, '');
      }
  
      // Compute current scroll position
      var scrollPos      = this.numScrollbackLines -
                        (this.scrollable.scrollTop-1) / this.cursorHeight;
  
      // Determine original cursor position. Hide cursor temporarily to avoid
      // visual artifacts.
      var hidden         = this.hideCursor();
      var cx             = this.cursorX;
      var cy             = this.cursorY;
      var console        = this.console[this.currentScreen];
  
      if (!incX && !x && w == this.terminalWidth) {
        // Scrolling entire lines
        if (incY < 0) {
          // Scrolling up
          if (!this.currentScreen && y == -incY &&
              h == this.terminalHeight + incY) {
            // Scrolling up with adding to the scrollback buffer. This is only
            // possible if there are at least as many lines in the console,
            // as the terminal is high
            while (console.childNodes.length < this.terminalHeight) {
              this.insertBlankLine(this.terminalHeight);
            }
  
            // Add new lines at bottom in order to force scrolling
            for (var i = 0; i < y; i++) {
              this.insertBlankLine(console.childNodes.length, color, style);
            }
  
            // Adjust the number of lines in the scrollback buffer by
            // removing excess entries.
            this.updateNumScrollbackLines();
            while (this.numScrollbackLines >
                   (this.currentScreen ? 0 : this.maxScrollbackLines)) {
              console.removeChild(console.firstChild);
              this.numScrollbackLines--;
            }
  
            // Mark lines in the scrollback buffer, so that they do not get
            // printed.
            for (var i = this.numScrollbackLines, j = -incY;
                 i-- > 0 && j-- > 0; ) {
              console.childNodes[i].className = 'scrollback';
            }
          } else {
            // Scrolling up without adding to the scrollback buffer.
            for (var i = -incY;
                 i-- > 0 &&
                 console.childNodes.length >
                 this.numScrollbackLines + y + incY; ) {
              console.removeChild(console.childNodes[
                                            this.numScrollbackLines + y + incY]);
            }
  
            // If we used to have a scrollback buffer, then we must make sure
            // that we add back blank lines at the bottom of the terminal.
            // Similarly, if we are scrolling in the middle of the screen,
            // we must add blank lines to ensure that the bottom of the screen
            // does not move up.
            if (this.numScrollbackLines > 0 ||
                console.childNodes.length > this.numScrollbackLines+y+h+incY) {
              for (var i = -incY; i-- > 0; ) {
                this.insertBlankLine(this.numScrollbackLines + y + h + incY,
                                     color, style);
              }
            }
          }
        } else {
          // Scrolling down
          for (var i = incY;
               i-- > 0 &&
               console.childNodes.length > this.numScrollbackLines + y + h; ) {
            console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
          }
          for (var i = incY; i--; ) {
            this.insertBlankLine(this.numScrollbackLines + y, color, style);
          }
        }
      } else {
        // Scrolling partial lines
        if (incY <= 0) {
          // Scrolling up or horizontally within a line
          for (var i = y + this.numScrollbackLines;
               i < y + this.numScrollbackLines + h;
               i++) {
            this.copyLineSegment(x + incX, i + incY, x, i, w);
          }
        } else {
          // Scrolling down
          for (var i = y + this.numScrollbackLines + h;
               i-- > y + this.numScrollbackLines; ) {
            this.copyLineSegment(x + incX, i + incY, x, i, w);
          }
        }
  
        // Clear blank regions
        if (incX > 0) {
          this.clearRegion(x, y, incX, h, color, style);
        } else if (incX < 0) {
          this.clearRegion(x + w + incX, y, -incX, h, color, style);
        }
        if (incY > 0) {
          this.clearRegion(x, y, w, incY, color, style);
        } else if (incY < 0) {
          this.clearRegion(x, y + h + incY, w, -incY, color, style);
        }
      }
  
      // Reset scroll position
      this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
                                  this.cursorHeight + 1;
  
      // Move cursor back to its original position
      hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
    }
  };
  
  VT100.prototype.copy = function(selection) {
    if (selection == undefined) {
      selection                = this.selection();
    }
    this.internalClipboard     = undefined;
    if (selection.length) {
      try {
        // IE
        this.cliphelper.value  = selection;
        this.cliphelper.select();
        this.cliphelper.createTextRange().execCommand('copy');
      } catch (e) {
        this.internalClipboard = selection;
      }
      this.cliphelper.value    = '';
    }
  };
  
  VT100.prototype.copyLast = function() {
    // Opening the context menu can remove the selection. We try to prevent this
    // from happening, but that is not possible for all browsers. So, instead,
    // we compute the selection before showing the menu.
    this.copy(this.lastSelection);
  };
  
  VT100.prototype.pasteFnc = function() {
    var clipboard     = undefined;
    if (this.internalClipboard != undefined) {
      clipboard       = this.internalClipboard;
    } else {
      try {
        this.cliphelper.value = '';
        this.cliphelper.createTextRange().execCommand('paste');
        clipboard     = this.cliphelper.value;
      } catch (e) {
      }
    }
    this.cliphelper.value = '';
    if (clipboard && this.menu.style.visibility == 'hidden') {
      return function() {
        this.keysPressed('' + clipboard);
      };
    } else {
      return undefined;
    }
  };
  
  VT100.prototype.pasteBrowserFnc = function() {
    var clipboard     = prompt("Paste into this box:","");
    if (clipboard != undefined) {
         return this.keysPressed('' + clipboard);
    }
  };
  
  VT100.prototype.toggleUTF = function() {
    this.utfEnabled   = !this.utfEnabled;
  
    // We always persist the last value that the user selected. Not necessarily
    // the last value that a random program requested.
    this.utfPreferred = this.utfEnabled;
  };
  
  VT100.prototype.toggleBell = function() {
    this.visualBell = !this.visualBell;
  };
  
  VT100.prototype.toggleSoftKeyboard = function() {
    this.softKeyboard = !this.softKeyboard;
    this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
  };
  
  VT100.prototype.toggleDisableAlt = function() {
    this.disableAlt = !this.disableAlt;
  };
  
  VT100.prototype.deselectKeys = function(elem) {
    if (elem && elem.className == 'selected') {
      elem.className = '';
    }
    for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
      this.deselectKeys(elem);
    }
  };
  
  VT100.prototype.showSoftKeyboard = function() {
    // Make sure no key is currently selected
    this.lastSelectedKey           = undefined;
    this.deselectKeys(this.keyboard);
    this.isShift                   = false;
    this.showShiftState(false);
    this.isCtrl                    = false;
    this.showCtrlState(false);
    this.isAlt                     = false;
    this.showAltState(false);
  
    this.keyboard.style.left       = '0px';
    this.keyboard.style.top        = '0px';
    this.keyboard.style.width      = this.container.offsetWidth  + 'px';
    this.keyboard.style.height     = this.container.offsetHeight + 'px';
    this.keyboard.style.visibility = 'hidden';
    this.keyboard.style.display    = '';
  
    var kbd                        = this.keyboard.firstChild;
    var scale                      = 1.0;
    var transform                  = this.getTransformName();
    if (transform) {
      kbd.style[transform]         = '';
      if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
        scale                      = (kbd.offsetWidth/
                                      this.container.offsetWidth)/0.9;
      }
      if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
        scale                      = Math.max((kbd.offsetHeight/
                                               this.container.offsetHeight)/0.9);
      }
      var style                    = this.getTransformStyle(transform,
                                                scale > 1.0 ? scale : undefined);
      kbd.style[transform]         = style;
    }
    if (transform == 'filter') {
      scale                        = 1.0;
    }
    kbd.style.left                 = ((this.container.offsetWidth -
                                       kbd.offsetWidth/scale)/2) + 'px';
    kbd.style.top                  = ((this.container.offsetHeight -
                                       kbd.offsetHeight/scale)/2) + 'px';
  
    this.keyboard.style.visibility = 'visible';
  };
  
  VT100.prototype.hideSoftKeyboard = function() {
    this.keyboard.style.display    = 'none';
  };
  
  VT100.prototype.toggleCursorBlinking = function() {
    this.blinkingCursor = !this.blinkingCursor;
  };
  
  VT100.prototype.about = function() {
    alert("VT100 Terminal Emulator " + "2.18  (revision c875886)" +
          "\nCopyright 2008-2010 by Markus Gutschke\n" +
          "For more information check http://shellinabox.com");
  };
  
  VT100.prototype.hideContextMenu = function() {
    this.menu.style.visibility = 'hidden';
    this.menu.style.top        = '-100px';
    this.menu.style.left       = '-100px';
    this.menu.style.width      = '0px';
    this.menu.style.height     = '0px';
  };
  
  VT100.prototype.extendContextMenu = function(entries, actions) {
  };
  
  VT100.prototype.showContextMenu = function(x, y) {
    this.menu.innerHTML         =
      '<table class="popup" ' +
             'cellpadding="0" cellspacing="0">' +
        '<tr><td>' +
          '<ul id="menuentries">' +
            '<li id="beginclipboard">Copy</li>' +
            '<li id="endclipboard">Paste</li>' +
            '<li id="browserclipboard">Paste from browser</li>' +
            '<hr />' +
            '<li id="reset">Reset</li>' +
            '<hr />' +
            '<li id="beginconfig">' +
               (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
               'Unicode</li>' +
            '<li>' +
               (this.visualBell ? '<img src="enabled.gif" />' : '') +
               'Visual Bell</li>'+
            '<li>' +
               (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
               'Onscreen Keyboard</li>' +
            '<li>' +
               (this.disableAlt ? '<img src="enabled.gif" />' : '') +
               'Disable Alt Key</li>' +
            '<li id="endconfig">' +
               (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
               'Blinking Cursor</li>'+
            (this.usercss.firstChild ?
             '<hr id="beginusercss" />' +
             this.usercss.innerHTML +
             '<hr id="endusercss" />' :
             '<hr />') +
            '<li id="about">About...</li>' +
          '</ul>' +
        '</td></tr>' +
      '</table>';
  
    var popup                   = this.menu.firstChild;
    var menuentries             = this.getChildById(popup, 'menuentries');
  
    // Determine menu entries that should be disabled
    this.lastSelection          = this.selection();
    if (!this.lastSelection.length) {
      menuentries.firstChild.className
                                = 'disabled';
    }
    var p                       = this.pasteFnc();
    if (!p) {
      menuentries.childNodes[1].className
                                = 'disabled';
    }
  
    // Actions for default items
    var actions                 = [ this.copyLast, p, this.pasteBrowserFnc, this.reset,
                                    this.toggleUTF, this.toggleBell,
                                    this.toggleSoftKeyboard,
                                    this.toggleDisableAlt,
                                    this.toggleCursorBlinking ];
  
    // Actions for user CSS styles (if any)
    for (var i = 0; i < this.usercssActions.length; ++i) {
      actions[actions.length]   = this.usercssActions[i];
    }
    actions[actions.length]     = this.about;
  
    // Allow subclasses to dynamically add entries to the context menu
    this.extendContextMenu(menuentries, actions);
  
    // Hook up event listeners
    for (var node = menuentries.firstChild, i = 0; node;
         node = node.nextSibling) {
      if (node.tagName == 'LI') {
        if (node.className != 'disabled') {
          this.addListener(node, 'mouseover',
                           function(vt100, node) {
                             return function() {
                               node.className = 'hover';
                             }
                           }(this, node));
          this.addListener(node, 'mouseout',
                           function(vt100, node) {
                             return function() {
                               node.className = '';
                             }
                           }(this, node));
          this.addListener(node, 'mousedown',
                           function(vt100, action) {
                             return function(event) {
                               vt100.hideContextMenu();
                               action.call(vt100);
                               vt100.storeUserSettings();
                               return vt100.cancelEvent(event || window.event);
                             }
                           }(this, actions[i]));
          this.addListener(node, 'mouseup',
                           function(vt100) {
                             return function(event) {
                               return vt100.cancelEvent(event || window.event);
                             }
                           }(this));
          this.addListener(node, 'mouseclick',
                           function(vt100) {
                             return function(event) {
                               return vt100.cancelEvent(event || window.event);
                             }
                           }());
        }
        i++;
      }
    }
  
    // Position menu next to the mouse pointer
    this.menu.style.left        = '0px';
    this.menu.style.top         = '0px';
    this.menu.style.width       =  this.container.offsetWidth  + 'px';
    this.menu.style.height      =  this.container.offsetHeight + 'px';
    popup.style.left            = '0px';
    popup.style.top             = '0px';
  
    var margin                  = 2;
    if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
      x              = this.container.offsetWidth-popup.clientWidth - margin - 1;
    }
    if (x < margin) {
      x                         = margin;
    }
    if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
      y            = this.container.offsetHeight-popup.clientHeight - margin - 1;
    }
    if (y < margin) {
      y                         = margin;
    }
    popup.style.left            = x + 'px';
    popup.style.top             = y + 'px';
  
    // Block all other interactions with the terminal emulator
    this.addListener(this.menu, 'click', function(vt100) {
                                           return function() {
                                             vt100.hideContextMenu();
                                           }
                                         }(this));
  
    // Show the menu
    this.menu.style.visibility  = '';
  };
  
  VT100.prototype.keysPressed = function(ch) {
    for (var i = 0; i < ch.length; i++) {
      var c = ch.charCodeAt(i);
      this.vt100(c >= 7 && c <= 15 ||
                 c == 24 || c == 26 || c == 27 || c >= 32
                 ? String.fromCharCode(c) : '<' + c + '>');
    }
  };
  
  VT100.prototype.applyModifiers = function(ch, event) {
    if (ch) {
      if (event.ctrlKey) {
        if (ch >= 32 && ch <= 127) {
          // For historic reasons, some control characters are treated specially
          switch (ch) {
          case /* 3 */ 51: ch  =  27; break;
          case /* 4 */ 52: ch  =  28; break;
          case /* 5 */ 53: ch  =  29; break;
          case /* 6 */ 54: ch  =  30; break;
          case /* 7 */ 55: ch  =  31; break;
          case /* 8 */ 56: ch  = 127; break;
          case /* ? */ 63: ch  = 127; break;
          default:         ch &=  31; break;
          }
        }
      }
      return String.fromCharCode(ch);
    } else {
      return undefined;
    }
  };
  
  VT100.prototype.handleKey = function(event) {
    // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
    //            (event.shiftKey || event.ctrlKey || event.altKey ||
    //             event.metaKey ? ', ' +
    //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
    //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
    //            '\r\n');
    var ch, key;
    if (typeof event.charCode != 'undefined') {
      // non-IE keypress events have a translated charCode value. Also, our
      // fake events generated when receiving keydown events include this data
      // on all browsers.
      ch                                = event.charCode;
      key                               = event.keyCode;
    } else {
      // When sending a keypress event, IE includes the translated character
      // code in the keyCode field.
      ch                                = event.keyCode;
      key                               = undefined;
    }
  
    // Apply modifier keys (ctrl and shift)
    if (ch) {
      key                               = undefined;
    }
    ch                                  = this.applyModifiers(ch, event);
  
    // By this point, "ch" is either defined and contains the character code, or
    // it is undefined and "key" defines the code of a function key
    if (ch != undefined) {
      this.scrollable.scrollTop         = this.numScrollbackLines *
                                          this.cursorHeight + 1;
    } else {
      if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
        // Many programs have difficulties dealing with parametrized escape
        // sequences for function keys. Thus, if ALT is the only modifier
        // key, return Emacs-style keycodes for commonly used keys.
        switch (key) {
        case  33: /* Page Up      */ ch = '\u001B<';                      break;
        case  34: /* Page Down    */ ch = '\u001B>';                      break;
        case  37: /* Left         */ ch = '\u001Bb';                      break;
        case  38: /* Up           */ ch = '\u001Bp';                      break;
        case  39: /* Right        */ ch = '\u001Bf';                      break;
        case  40: /* Down         */ ch = '\u001Bn';                      break;
        case  46: /* Delete       */ ch = '\u001Bd';                      break;
        default:                                                          break;
        }
      } else if (event.shiftKey && !event.ctrlKey &&
                 !event.altKey && !event.metaKey) {
        switch (key) {
        case  33: /* Page Up      */ this.scrollBack();                   return;
        case  34: /* Page Down    */ this.scrollFore();                   return;
        default:                                                          break;
        }
      }
      if (ch == undefined) {
        switch (key) {
        case   8: /* Backspace    */ ch = '\u007f';                       break;
        case   9: /* Tab          */ ch = '\u0009';                       break;
        case  10: /* Return       */ ch = '\u000A';                       break;
        case  13: /* Enter        */ ch = this.crLfMode ?
                                          '\r\n' : '\r';                  break;
        case  16: /* Shift        */                                      return;
        case  17: /* Ctrl         */                                      return;
        case  18: /* Alt          */                                      return;
        case  19: /* Break        */                                      return;
        case  20: /* Caps Lock    */                                      return;
        case  27: /* Escape       */ ch = '\u001B';                       break;
        case  33: /* Page Up      */ ch = '\u001B[5~';                    break;
        case  34: /* Page Down    */ ch = '\u001B[6~';                    break;
        case  35: /* End          */ ch = '\u001BOF';                     break;
        case  36: /* Home         */ ch = '\u001BOH';                     break;
        case  37: /* Left         */ ch = this.cursorKeyMode ?
                               '\u001BOD' : '\u001B[D';                   break;
        case  38: /* Up           */ ch = this.cursorKeyMode ?
                               '\u001BOA' : '\u001B[A';                   break;
        case  39: /* Right        */ ch = this.cursorKeyMode ?
                               '\u001BOC' : '\u001B[C';                   break;
        case  40: /* Down         */ ch = this.cursorKeyMode ?
                               '\u001BOB' : '\u001B[B';                   break;
        case  45: /* Insert       */ ch = '\u001B[2~';                    break;
        case  46: /* Delete       */ ch = '\u001B[3~';                    break;
        case  91: /* Left Window  */                                      return;
        case  92: /* Right Window */                                      return;
        case  93: /* Select       */                                      return;
        case  96: /* 0            */ ch = this.applyModifiers(48, event); break;
        case  97: /* 1            */ ch = this.applyModifiers(49, event); break;
        case  98: /* 2            */ ch = this.applyModifiers(50, event); break;
        case  99: /* 3            */ ch = this.applyModifiers(51, event); break;
        case 100: /* 4            */ ch = this.applyModifiers(52, event); break;
        case 101: /* 5            */ ch = this.applyModifiers(53, event); break;
        case 102: /* 6            */ ch = this.applyModifiers(54, event); break;
        case 103: /* 7            */ ch = this.applyModifiers(55, event); break;
        case 104: /* 8            */ ch = this.applyModifiers(56, event); break;
        case 105: /* 9            */ ch = this.applyModifiers(58, event); break;
        case 106: /* *            */ ch = this.applyModifiers(42, event); break;
        case 107: /* +            */ ch = this.applyModifiers(43, event); break;
        case 109: /* -            */ ch = this.applyModifiers(45, event); break;
        case 110: /* .            */ ch = this.applyModifiers(46, event); break;
        case 111: /* /            */ ch = this.applyModifiers(47, event); break;
        case 112: /* F1           */ ch = '\u001BOP';                     break;
        case 113: /* F2           */ ch = '\u001BOQ';                     break;
        case 114: /* F3           */ ch = '\u001BOR';                     break;
        case 115: /* F4           */ ch = '\u001BOS';                     break;
        case 116: /* F5           */ ch = '\u001B[15~';                   break;
        case 117: /* F6           */ ch = '\u001B[17~';                   break;
        case 118: /* F7           */ ch = '\u001B[18~';                   break;
        case 119: /* F8           */ ch = '\u001B[19~';                   break;
        case 120: /* F9           */ ch = '\u001B[20~';                   break;
        case 121: /* F10          */ ch = '\u001B[21~';                   break;
        case 122: /* F11          */ ch = '\u001B[23~';                   break;
        case 123: /* F12          */ ch = '\u001B[24~';                   break;
        case 144: /* Num Lock     */                                      return;
        case 145: /* Scroll Lock  */                                      return;
        case 186: /* ;            */ ch = this.applyModifiers(59, event); break;
        // Conflicts with dead keys ` on Danish keyboard
        //                          ¸ on Slovenian keyboard
        // case 187: /* =            */ ch = this.applyModifiers(61, event); break;
        case 188: /* ,            */ ch = this.applyModifiers(44, event); break;
        case 189: /* -            */ ch = this.applyModifiers(45, event); break;
        case 190: /* .            */ ch = this.applyModifiers(46, event); break;
        case 191: /* /            */ ch = this.applyModifiers(47, event); break;
        // Conflicts with dead key " on Swiss keyboards
        // case 192: /* `            */ ch = this.applyModifiers(96, event); break;
        // Conflicts with dead key " on Swiss keyboards
        // case 219: /* [            */ ch = this.applyModifiers(91, event); break;
        case 220: /* \            */ ch = this.applyModifiers(92, event); break;
        // Conflicts with dead key ^ and ` on Swiss keaboards
        //                         ^ and " on French keyboards
        // case 221: /* ]            */ ch = this.applyModifiers(93, event); break;
        case 222: /* '            */ ch = this.applyModifiers(39, event); break;
        default:                                                          return;
        }
        this.scrollable.scrollTop       = this.numScrollbackLines *
                                          this.cursorHeight + 1;
      }
    }
  
    // "ch" now contains the sequence of keycodes to send. But we might still
    // have to apply the effects of modifier keys.
    if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
      var start, digit, part1, part2;
      if ((start = ch.substr(0, 2)) == '\u001B[') {
        for (part1 = start;
             part1.length < ch.length &&
               (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
          part1                         = ch.substr(0, part1.length + 1);
        }
        part2                           = ch.substr(part1.length);
        if (part1.length > 2) {
          part1                        += ';';
        }
      } else if (start == '\u001BO') {
        part1                           = start;
        part2                           = ch.substr(2);
      }
      if (part1 != undefined) {
        ch                              = part1                                 +
                                         ((event.shiftKey             ? 1 : 0)  +
                                          (event.altKey|event.metaKey ? 2 : 0)  +
                                          (event.ctrlKey              ? 4 : 0)) +
                                          part2;
      } else if (ch.length == 1 && (event.altKey || event.metaKey)
                 && !this.disableAlt) {
        ch                              = '\u001B' + ch;
      }
    }
  
    if (this.menu.style.visibility == 'hidden') {
      // this.vt100('R: c=');
      // for (var i = 0; i < ch.length; i++)
      //   this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
      // this.vt100('\r\n');
      this.keysPressed(ch);
    }
  };
  
  VT100.prototype.inspect = function(o, d) {
    if (d == undefined) {
      d       = 0;
    }
    var rc    = '';
    if (typeof o == 'object' && ++d < 2) {
      rc      = '[\r\n';
      for (i in o) {
        rc   += this.spaces(d * 2) + i + ' -> ';
        try {
          rc += this.inspect(o[i], d);
        } catch (e) {
          rc += '?' + '?' + '?\r\n';
        }
      }
      rc     += ']\r\n';
    } else {
      rc     += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
    }
    return rc;
  };
  
  VT100.prototype.checkComposedKeys = function(event) {
    // Composed keys (at least on Linux) do not generate normal events.
    // Instead, they get entered into the text field. We normally catch
    // this on the next keyup event.
    var s              = this.input.value;
    if (s.length) {
      this.input.value = '';
      if (this.menu.style.visibility == 'hidden') {
        this.keysPressed(s);
      }
    }
  };
  
  VT100.prototype.fixEvent = function(event) {
    // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
    // is used as a second-level selector, clear the modifier bits before
    // handling the event.
    if (event.ctrlKey && event.altKey) {
      var fake                = [ ];
      fake.charCode           = event.charCode;
      fake.keyCode            = event.keyCode;
      fake.ctrlKey            = false;
      fake.shiftKey           = event.shiftKey;
      fake.altKey             = false;
      fake.metaKey            = event.metaKey;
      return fake;
    }
  
    // Some browsers fail to translate keys, if both shift and alt/meta is
    // pressed at the same time. We try to translate those cases, but that
    // only works for US keyboard layouts.
    if (event.shiftKey) {
      var u                   = undefined;
      var s                   = undefined;
      switch (this.lastNormalKeyDownEvent.keyCode) {
      case  39: /* ' -> " */ u = 39; s =  34; break;
      case  44: /* , -> < */ u = 44; s =  60; break;
      case  45: /* - -> _ */ u = 45; s =  95; break;
      case  46: /* . -> > */ u = 46; s =  62; break;
      case  47: /* / -> ? */ u = 47; s =  63; break;
  
      case  48: /* 0 -> ) */ u = 48; s =  41; break;
      case  49: /* 1 -> ! */ u = 49; s =  33; break;
      case  50: /* 2 -> @ */ u = 50; s =  64; break;
      case  51: /* 3 -> # */ u = 51; s =  35; break;
      case  52: /* 4 -> $ */ u = 52; s =  36; break;
      case  53: /* 5 -> % */ u = 53; s =  37; break;
      case  54: /* 6 -> ^ */ u = 54; s =  94; break;
      case  55: /* 7 -> & */ u = 55; s =  38; break;
      case  56: /* 8 -> * */ u = 56; s =  42; break;
      case  57: /* 9 -> ( */ u = 57; s =  40; break;
      case  59: /* ; -> : */ u = 59; s =  58; break;
      case  61: /* = -> + */ u = 61; s =  43; break;
      case  91: /* [ -> { */ u = 91; s = 123; break;
      case  92: /* \ -> | */ u = 92; s = 124; break;
      case  93: /* ] -> } */ u = 93; s = 125; break;
      case  96: /* ` -> ~ */ u = 96; s = 126; break;
  
      case 109: /* - -> _ */ u = 45; s =  95; break;
      case 111: /* / -> ? */ u = 47; s =  63; break;
  
      case 186: /* ; -> : */ u = 59; s =  58; break;
      case 187: /* = -> + */ u = 61; s =  43; break;
      case 188: /* , -> < */ u = 44; s =  60; break;
      case 189: /* - -> _ */ u = 45; s =  95; break;
      case 190: /* . -> > */ u = 46; s =  62; break;
      case 191: /* / -> ? */ u = 47; s =  63; break;
      case 192: /* ` -> ~ */ u = 96; s = 126; break;
      case 219: /* [ -> { */ u = 91; s = 123; break;
      case 220: /* \ -> | */ u = 92; s = 124; break;
      case 221: /* ] -> } */ u = 93; s = 125; break;
      case 222: /* ' -> " */ u = 39; s =  34; break;
      default:                                break;
      }
      if (s && (event.charCode == u || event.charCode == 0)) {
        var fake              = [ ];
        fake.charCode         = s;
        fake.keyCode          = event.keyCode;
        fake.ctrlKey          = event.ctrlKey;
        fake.shiftKey         = event.shiftKey;
        fake.altKey           = event.altKey;
        fake.metaKey          = event.metaKey;
        return fake;
      }
    }
    return event;
  };
  
  VT100.prototype.keyDown = function(event) {
    // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
    //            (event.shiftKey || event.ctrlKey || event.altKey ||
    //             event.metaKey ? ', ' +
    //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
    //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
    //            '\r\n');
    this.checkComposedKeys(event);
    this.lastKeyPressedEvent      = undefined;
    this.lastKeyDownEvent         = undefined;
    this.lastNormalKeyDownEvent   = event;
  
    // Swiss keyboard conflicts:
    // [ 59
    // ] 192
    // ' 219 (dead key)
    // { 220
    // ~ 221 (dead key)
    // } 223
    // French keyoard conflicts:
    // ~ 50 (dead key)
    // } 107
    var asciiKey                  =
      event.keyCode ==  32                         ||
      event.keyCode >=  48 && event.keyCode <=  57 ||
      event.keyCode >=  65 && event.keyCode <=  90;
    var alphNumKey                =
      asciiKey                                     ||
      event.keyCode >=  58 && event.keyCode <=  64 ||
      event.keyCode >=  96 && event.keyCode <= 105 ||
      event.keyCode == 107 ||
      event.keyCode >= 160 && event.keyCode <= 192 ||
      event.keyCode >= 219 && event.keyCode <= 223 ||
      event.keyCode == 226;
    var normalKey                 =
      alphNumKey                                   ||
      event.keyCode == 106                         ||
      event.keyCode >= 109 && event.keyCode <= 111 ||
      event.keyCode == 229                         ||
      event.keyCode == 252;
    try {
      if (navigator.appName == 'Konqueror') {
        normalKey                |= event.keyCode < 128;
      }
    } catch (e) {
    }
  
    if (this.disableAlt && normalKey) {
      return true;
    }
  
    // We normally prefer to look at keypress events, as they perform the
    // translation from keyCode to charCode. This is important, as the
    // translation is locale-dependent.
    // But for some keys, we must intercept them during the keydown event,
    // as they would otherwise get interpreted by the browser.
    // Even, when doing all of this, there are some keys that we can never
    // intercept. This applies to some of the menu navigation keys in IE.
    // In fact, we see them, but we cannot stop IE from seeing them, too.
    if ((event.charCode || event.keyCode) &&
        ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
          !event.shiftKey &&
          // Some browsers signal AltGR as both CTRL and ALT. Do not try to
          // interpret this sequence ourselves, as some keyboard layouts use
          // it for second-level layouts.
          !(event.ctrlKey && event.altKey)) ||
         this.catchModifiersEarly && normalKey && !alphNumKey &&
         (event.ctrlKey || event.altKey || event.metaKey) ||
         !normalKey)) {
      this.lastKeyDownEvent       = event;
      var fake                    = [ ];
      fake.ctrlKey                = event.ctrlKey;
      fake.shiftKey               = event.shiftKey;
      fake.altKey                 = event.altKey;
      fake.metaKey                = event.metaKey;
      if (asciiKey) {
        fake.charCode             = event.keyCode;
        fake.keyCode              = 0;
      } else {
        fake.charCode             = 0;
        fake.keyCode              = event.keyCode;
        if (!alphNumKey && event.shiftKey) {
          fake                    = this.fixEvent(fake);
        }
      }
  
      this.handleKey(fake);
      this.lastNormalKeyDownEvent = undefined;
  
      try {
        // For non-IE browsers
        event.stopPropagation();
        event.preventDefault();
      } catch (e) {
      }
      try {
        // For IE
        event.cancelBubble = true;
        event.returnValue  = false;
        event.keyCode      = 0;
      } catch (e) {
      }
  
      return false;
    }
    return true;
  };
  
  VT100.prototype.keyPressed = function(event) {
    // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
    //            (event.shiftKey || event.ctrlKey || event.altKey ||
    //             event.metaKey ? ', ' +
    //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
    //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
    //            '\r\n');
    if (this.lastKeyDownEvent) {
      // If we already processed the key on keydown, do not process it
      // again here. Ideally, the browser should not even have generated a
      // keypress event in this case. But that does not appear to always work.
      this.lastKeyDownEvent     = undefined;
    } else {
      this.handleKey(event.altKey || event.metaKey
                     ? this.fixEvent(event) : event);
    }
  
    try {
      // For non-IE browsers
      event.preventDefault();
    } catch (e) {
    }
  
    try {
      // For IE
      event.cancelBubble = true;
      event.returnValue  = false;
      event.keyCode      = 0;
    } catch (e) {
    }
  
    this.lastNormalKeyDownEvent = undefined;
    this.lastKeyPressedEvent    = event;
    return false;
  };
  
  VT100.prototype.keyUp = function(event) {
    // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
    //            (event.shiftKey || event.ctrlKey || event.altKey ||
    //             event.metaKey ? ', ' +
    //             (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
    //             (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
    //            '\r\n');
    if (this.lastKeyPressedEvent) {
      // The compose key on Linux occasionally confuses the browser and keeps
      // inserting bogus characters into the input field, even if just a regular
      // key has been pressed. Detect this case and drop the bogus characters.
      (event.target ||
       event.srcElement).value      = '';
    } else {
      // This is usually were we notice that a key has been composed and
      // thus failed to generate normal events.
      this.checkComposedKeys(event);
  
      // Some browsers don't report keypress events if ctrl or alt is pressed
      // for non-alphanumerical keys. Patch things up for now, but in the
      // future we will catch these keys earlier (in the keydown handler).
      if (this.lastNormalKeyDownEvent) {
        // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
        this.catchModifiersEarly    = true;
        var asciiKey                =
          event.keyCode ==  32                         ||
          // Conflicts with dead key ~ (code 50) on French keyboards
          //event.keyCode >=  48 && event.keyCode <=  57 ||
          event.keyCode >=  48 && event.keyCode <=  49 ||
          event.keyCode >=  51 && event.keyCode <=  57 ||
          event.keyCode >=  65 && event.keyCode <=  90;
        var alphNumKey              =
          asciiKey                                     ||
          event.keyCode ==  50                         ||
          event.keyCode >=  96 && event.keyCode <= 105;
        // Not used ???
        var normalKey               =
          alphNumKey                                   ||
          event.keyCode ==  59 || event.keyCode ==  61 ||
          event.keyCode == 106 || event.keyCode == 107 ||
          event.keyCode >= 109 && event.keyCode <= 111 ||
          event.keyCode >= 186 && event.keyCode <= 192 ||
          event.keyCode >= 219 && event.keyCode <= 223 ||
          event.keyCode == 252;
        var fake                    = [ ];
        fake.ctrlKey                = event.ctrlKey;
        fake.shiftKey               = event.shiftKey;
        fake.altKey                 = event.altKey;
        fake.metaKey                = event.metaKey;
        if (asciiKey) {
          fake.charCode             = event.keyCode;
          fake.keyCode              = 0;
        } else {
          fake.charCode             = 0;
          fake.keyCode              = event.keyCode;
          if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
            fake                    = this.fixEvent(fake);
          }
        }
        this.lastNormalKeyDownEvent = undefined;
        this.handleKey(fake);
      }
    }
  
    try {
      // For IE
      event.cancelBubble            = true;
      event.returnValue             = false;
      event.keyCode                 = 0;
    } catch (e) {
    }
  
    this.lastKeyDownEvent           = undefined;
    this.lastKeyPressedEvent        = undefined;
    return false;
  };
  
  VT100.prototype.animateCursor = function(inactive) {
    if (!this.cursorInterval) {
      this.cursorInterval       = setInterval(
        function(vt100) {
          return function() {
            vt100.animateCursor();
  
            // Use this opportunity to check whether the user entered a composed
            // key, or whether somebody pasted text into the textfield.
            vt100.checkComposedKeys();
          }
        }(this), 500);
    }
    if (inactive != undefined || this.cursor.className != 'inactive') {
      if (inactive) {
        this.cursor.className   = 'inactive';
      } else {
        if (this.blinkingCursor) {
          this.cursor.className = this.cursor.className == 'bright'
                                  ? 'dim' : 'bright';
        } else {
          this.cursor.className = 'bright';
        }
      }
    }
  };
  
  VT100.prototype.blurCursor = function() {
    this.animateCursor(true);
  };
  
  VT100.prototype.focusCursor = function() {
    this.animateCursor(false);
  };
  
  VT100.prototype.flashScreen = function() {
    this.isInverted       = !this.isInverted;
    this.refreshInvertedState();
    this.isInverted       = !this.isInverted;
    setTimeout(function(vt100) {
                 return function() {
                   vt100.refreshInvertedState();
                 };
               }(this), 100);
  };
  
  VT100.prototype.beep = function() {
    if (this.visualBell) {
      this.flashScreen();
    } else {
      try {
        this.beeper.Play();
      } catch (e) {
        try {
          this.beeper.src = 'beep.wav';
        } catch (e) {
        }
      }
    }
  };
  
  VT100.prototype.bs = function() {
    if (this.cursorX > 0) {
      this.gotoXY(this.cursorX - 1, this.cursorY);
      this.needWrap = false;
    }
  };
  
  VT100.prototype.ht = function(count) {
    if (count == undefined) {
      count        = 1;
    }
    var cx         = this.cursorX;
    while (count-- > 0) {
      while (cx++ < this.terminalWidth) {
        var tabState = this.userTabStop[cx];
        if (tabState == false) {
          // Explicitly cleared tab stop
          continue;
        } else if (tabState) {
          // Explicitly set tab stop
          break;
        } else {
          // Default tab stop at each eighth column
          if (cx % 8 == 0) {
            break;
          }
        }
      }
    }
    if (cx > this.terminalWidth - 1) {
      cx           = this.terminalWidth - 1;
    }
    if (cx != this.cursorX) {
      this.gotoXY(cx, this.cursorY);
    }
  };
  
  VT100.prototype.rt = function(count) {
    if (count == undefined) {
      count          = 1 ;
    }
    var cx           = this.cursorX;
    while (count-- > 0) {
      while (cx-- > 0) {
        var tabState = this.userTabStop[cx];
        if (tabState == false) {
          // Explicitly cleared tab stop
          continue;
        } else if (tabState) {
          // Explicitly set tab stop
          break;
        } else {
          // Default tab stop at each eighth column
          if (cx % 8 == 0) {
            break;
          }
        }
      }
    }
    if (cx < 0) {
      cx             = 0;
    }
    if (cx != this.cursorX) {
      this.gotoXY(cx, this.cursorY);
    }
  };
  
  VT100.prototype.cr = function() {
    this.gotoXY(0, this.cursorY);
    this.needWrap = false;
  };
  
  VT100.prototype.lf = function(count) {
    if (count == undefined) {
      count    = 1;
    } else {
      if (count > this.terminalHeight) {
        count  = this.terminalHeight;
      }
      if (count < 1) {
        count  = 1;
      }
    }
    while (count-- > 0) {
      if (this.cursorY == this.bottom - 1) {
        this.scrollRegion(0, this.top + 1,
                          this.terminalWidth, this.bottom - this.top - 1,
                          0, -1, this.color, this.style);
      } else if (this.cursorY < this.terminalHeight - 1) {
        this.gotoXY(this.cursorX, this.cursorY + 1);
      }
    }
  };
  
  VT100.prototype.ri = function(count) {
    if (count == undefined) {
      count   = 1;
    } else {
      if (count > this.terminalHeight) {
        count = this.terminalHeight;
      }
      if (count < 1) {
        count = 1;
      }
    }
    while (count-- > 0) {
      if (this.cursorY == this.top) {
        this.scrollRegion(0, this.top,
                          this.terminalWidth, this.bottom - this.top - 1,
                          0, 1, this.color, this.style);
      } else if (this.cursorY > 0) {
        this.gotoXY(this.cursorX, this.cursorY - 1);
      }
    }
    this.needWrap = false;
  };
  
  VT100.prototype.respondID = function() {
    this.respondString += '\u001B[?6c';
  };
  
  VT100.prototype.respondSecondaryDA = function() {
    this.respondString += '\u001B[>0;0;0c';
  };
  
  
  VT100.prototype.updateStyle = function() {
    var fg          = '';
    var bg          = '';
    this.style      = '';
  
    if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
      this.style   += 'text-decoration: underline;';
    }
    if (this.attr & 0x1000 /* ATTR_BLINK */) {
      this.style   += 'text-decoration: blink;';
    }
  
    // Forground color
    if (this.attrFg) {
      // 256 color mode
      fg            = this.attrFg
    } else if (this.attr & 0x2000 /* ATTR_DEF_FG */) {
      fg            = 'Def';
    } else {
      fg            = this.attr & 0xF;
      if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
        fg         |= 8;
        this.style += 'font-weight: bold;';
      }
    }
  
    // Background color
    if (this.attrBg) {
      // 256 color mode
      bg            = this.attrBg
    } else if (this.attr & 0x4000 /* ATTR_DEF_BG */) {
      bg            = 'Def';
    } else {
      bg            = (this.attr >> 4) & 0xF;
    }
  
    // Reverse colors
    if (this.attr & 0x0100 /* ATTR_REVERSE */) {
      var tmpFg     = fg;
      var tmpBg     = bg;
      fg            = (tmpBg == 'Def') ? 'DefR' : tmpBg;
      bg            = (tmpFg == 'Def') ? 'DefR' : tmpFg;
    }
  
    this.color      = 'ansi' + fg + ' bgAnsi' + bg;
  };
  
  VT100.prototype.setAttrColors = function(attr) {
    if (attr != this.attr) {
      this.attr = attr;
      this.updateStyle();
    }
  };
  
  VT100.prototype.saveCursor = function() {
    this.savedX[this.currentScreen]      = this.cursorX;
    this.savedY[this.currentScreen]      = this.cursorY;
    this.savedAttr[this.currentScreen]   = this.attr;
    this.savedAttrFg[this.currentScreen] = this.attrFg;
    this.savedAttrBg[this.currentScreen] = this.attrBg;
    this.savedUseGMap                    = this.useGMap;
    for (var i = 0; i < 4; i++) {
      this.savedGMap[i]                  = this.GMap[i];
    }
    this.savedValid[this.currentScreen]  = true;
  };
  
  VT100.prototype.restoreCursor = function() {
    if (!this.savedValid[this.currentScreen]) {
      return;
    }
    this.attr      = this.savedAttr[this.currentScreen];
    this.attrFg    = this.savedAttrFg[this.currentScreen];
    this.attrBg    = this.savedAttrBg[this.currentScreen];
    this.updateStyle();
    this.useGMap   = this.savedUseGMap;
    for (var i = 0; i < 4; i++) {
      this.GMap[i] = this.savedGMap[i];
    }
    this.translate = this.GMap[this.useGMap];
    this.needWrap  = false;
    this.gotoXY(this.savedX[this.currentScreen],
                this.savedY[this.currentScreen]);
  };
  
  VT100.prototype.getTransformName = function() {
    var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
    for (var i = 0; i < styles.length; ++i) {
      if (typeof this.console[0].style[styles[i]] != 'undefined') {
        return styles[i];
      }
    }
    return undefined;
  };
  
  VT100.prototype.getTransformStyle = function(transform, scale) {
    return scale && scale != 1.0
      ? transform == 'filter'
        ? 'progid:DXImageTransform.Microsoft.Matrix(' +
                                   'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
                                   "sizingMethod='auto expand')"
        : 'translateX(-50%) ' +
          'scaleX(' + (1.0/scale) + ') ' +
          'translateX(50%)'
      : '';
  };
  
  VT100.prototype.set80_132Mode = function(state) {
    var transform                  = this.getTransformName();
    if (transform) {
      if ((this.console[this.currentScreen].style[transform] != '') == state) {
        return;
      }
      var style                    = state ?
                                     this.getTransformStyle(transform, 1.65):'';
      this.console[this.currentScreen].style[transform] = style;
      this.cursor.style[transform] = style;
      this.space.style[transform]  = style;
      this.scale                   = state ? 1.65 : 1.0;
      if (transform == 'filter') {
        this.console[this.currentScreen].style.width = state ? '165%' : '';
      }
      this.resizer();
    }
  };
  
  VT100.prototype.setMode = function(state) {
    for (var i = 0; i <= this.npar; i++) {
      if (this.isQuestionMark) {
        switch (this.par[i]) {
        case  1: this.cursorKeyMode      = state;                      break;
        case  3: this.set80_132Mode(state);                            break;
        case  5: this.isInverted = state; this.refreshInvertedState(); break;
        case  6: this.offsetMode         = state;                      break;
        case  7: this.autoWrapMode       = state;                      break;
        case 1000:
        case  9: this.mouseReporting     = state;                      break;
        case 25: this.cursorNeedsShowing = state;
                 if (state) { this.showCursor(); }
                 else       { this.hideCursor(); }                     break;
        case 1047:
        case 1049:
        case 47: this.enableAlternateScreen(state);                    break;
        default:                                                       break;
        }
      } else {
        switch (this.par[i]) {
        case  3: this.dispCtrl           = state;                      break;
        case  4: this.insertMode         = state;                      break;
        case  20:this.crLfMode           = state;                      break;
        default:                                                       break;
        }
      }
    }
  };
  
  VT100.prototype.statusReport = function() {
    // Ready and operational.
    this.respondString += '\u001B[0n';
  };
  
  VT100.prototype.cursorReport = function() {
    this.respondString += '\u001B[' +
                          (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
                          ';' +
                          (this.cursorX + 1) +
                          'R';
  };
  
  VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
    // Changing of cursor color is not implemented.
  };
  
  VT100.prototype.openPrinterWindow = function() {
    var rc            = true;
    try {
      if (!this.printWin || this.printWin.closed) {
        this.printWin = window.open('', 'print-output',
          'width=800,height=600,directories=no,location=no,menubar=yes,' +
          'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
        this.printWin.document.body.innerHTML =
          '<link rel="stylesheet" href="' +
            document.location.protocol + '//' + document.location.host +
            document.location.pathname.replace(/[^/]*$/, '') +
            'print-styles.css" type="text/css">\n' +
          '<div id="options"><input id="autoprint" type="checkbox"' +
            (this.autoprint ? ' checked' : '') + '>' +
            'Automatically, print page(s) when job is ready' +
          '</input></div>\n' +
          '<div id="spacer"><input type="checkbox">&nbsp;</input></div>' +
          '<pre id="print"></pre>\n';
        var autoprint = this.printWin.document.getElementById('autoprint');
        this.addListener(autoprint, 'click',
                         (function(vt100, autoprint) {
                           return function() {
                             vt100.autoprint = autoprint.checked;
                             vt100.storeUserSettings();
                             return false;
                           };
                         })(this, autoprint));
        this.printWin.document.title = 'ShellInABox Printer Output';
      }
    } catch (e) {
      // Maybe, a popup blocker prevented us from working. Better catch the
      // exception, so that we won't break the entire terminal session. The
      // user probably needs to disable the blocker first before retrying the
      // operation.
      rc              = false;
    }
    rc               &= this.printWin && !this.printWin.closed &&
                        (this.printWin.innerWidth ||
                         this.printWin.document.documentElement.clientWidth ||
                         this.printWin.document.body.clientWidth) > 1;
  
    if (!rc && this.printing == 100) {
      // Different popup blockers work differently. We try to detect a couple
      // of common methods. And then we retry again a brief amount later, as
      // false positives are otherwise possible. If we are sure that there is
      // a popup blocker in effect, we alert the user to it. This is helpful
      // as some popup blockers have minimal or no UI, and the user might not
      // notice that they are missing the popup. In any case, we only show at
      // most one message per print job.
      this.printing   = true;
      setTimeout((function(win) {
                    return function() {
                      if (!win || win.closed ||
                          (win.innerWidth ||
                           win.document.documentElement.clientWidth ||
                           win.document.body.clientWidth) <= 1) {
                        alert('Attempted to print, but a popup blocker ' +
                              'prevented the printer window from opening');
                      }
                    };
                  })(this.printWin), 2000);
    }
    return rc;
  };
  
  VT100.prototype.sendToPrinter = function(s) {
    this.openPrinterWindow();
    try {
      var doc   = this.printWin.document;
      var print = doc.getElementById('print');
      if (print.lastChild && print.lastChild.nodeName == '#text') {
        print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
      } else {
        print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
      }
    } catch (e) {
      // There probably was a more aggressive popup blocker that prevented us
      // from accessing the printer windows.
    }
  };
  
  VT100.prototype.sendControlToPrinter = function(ch) {
    // We get called whenever doControl() is active. But for the printer, we
    // only implement a basic line printer that doesn't understand most of
    // the escape sequences of the VT100 terminal. In fact, the only escape
    // sequence that we really need to recognize is '^[[5i' for turning the
    // printer off.
    try {
      switch (ch) {
      case  9:
        // HT
        this.openPrinterWindow();
        var doc                 = this.printWin.document;
        var print               = doc.getElementById('print');
        var chars               = print.lastChild &&
                                  print.lastChild.nodeName == '#text' ?
                                  print.lastChild.textContent.length : 0;
        this.sendToPrinter(this.spaces(8 - (chars % 8)));
        break;
      case 10:
        // CR
        break;
      case 12:
        // FF
        this.openPrinterWindow();
        var pageBreak           = this.printWin.document.createElement('div');
        pageBreak.className     = 'pagebreak';
        pageBreak.innerHTML     = '<hr />';
        this.printWin.document.getElementById('print').appendChild(pageBreak);
        break;
      case 13:
        // LF
        this.openPrinterWindow();
        var lineBreak           = this.printWin.document.createElement('br');
        this.printWin.document.getElementById('print').appendChild(lineBreak);
        break;
      case 27:
        // ESC
        this.isEsc              = 1 /* ESesc */;
        break;
      default:
        switch (this.isEsc) {
        case 1 /* ESesc */:
          this.isEsc            = 0 /* ESnormal */;
          switch (ch) {
          case 0x5B /*[*/:
            this.isEsc          = 2 /* ESsquare */;
            break;
          default:
            break;
          }
          break;
        case 2 /* ESsquare */:
          this.npar             = 0;
          this.par              = [ 0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0 ];
          this.isEsc            = 3 /* ESgetpars */;
          this.isQuestionMark   = ch == 0x3F /*?*/;
          if (this.isQuestionMark) {
            break;
          }
          // Fall through
        case 3 /* ESgetpars */:
          if (ch == 0x3B /*;*/) {
            this.npar++;
            break;
          } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
            var par             = this.par[this.npar];
            if (par == undefined) {
              par               = 0;
            }
            this.par[this.npar] = 10*par + (ch & 0xF);
            break;
          } else {
            this.isEsc          = 4 /* ESgotpars */;
          }
          // Fall through
        case 4 /* ESgotpars */:
          this.isEsc            = 0 /* ESnormal */;
          if (this.isQuestionMark) {
            break;
          }
          switch (ch) {
          case 0x69 /*i*/:
            this.csii(this.par[0]);
            break;
          default:
            break;
          }
          break;
        default:
          this.isEsc            = 0 /* ESnormal */;
          break;
        }
        break;
      }
    } catch (e) {
      // There probably was a more aggressive popup blocker that prevented us
      // from accessing the printer windows.
    }
  };
  
  VT100.prototype.csiAt = function(number) {
    // Insert spaces
    if (number == 0) {
      number      = 1;
    }
    if (number > this.terminalWidth - this.cursorX) {
      number      = this.terminalWidth - this.cursorX;
    }
    this.scrollRegion(this.cursorX, this.cursorY,
                      this.terminalWidth - this.cursorX - number, 1,
                      number, 0, this.color, this.style);
    this.needWrap = false;
  };
  
  VT100.prototype.csii = function(number) {
    // Printer control
    switch (number) {
    case 0: // Print Screen
      window.print();
      break;
    case 4: // Stop printing
      try {
        if (this.printing && this.printWin && !this.printWin.closed) {
          var print = this.printWin.document.getElementById('print');
          while (print.lastChild &&
                 print.lastChild.tagName == 'DIV' &&
                 print.lastChild.className == 'pagebreak') {
            // Remove trailing blank pages
            print.removeChild(print.lastChild);
          }
          if (this.autoprint) {
            this.printWin.print();
          }
        }
      } catch (e) {
      }
      this.printing = false;
      break;
    case 5: // Start printing
      if (!this.printing && this.printWin && !this.printWin.closed) {
        this.printWin.document.getElementById('print').innerHTML = '';
      }
      this.printing = 100;
      break;
    default:
      break;
    }
  };
  
  VT100.prototype.csiJ = function(number) {
    switch (number) {
    case 0: // Erase from cursor to end of display
      this.clearRegion(this.cursorX, this.cursorY,
                       this.terminalWidth - this.cursorX, 1,
                       this.color, this.style);
      if (this.cursorY < this.terminalHeight-2) {
        this.clearRegion(0, this.cursorY+1,
                         this.terminalWidth, this.terminalHeight-this.cursorY-1,
                         this.color, this.style);
      }
      break;
    case 1: // Erase from start to cursor
      if (this.cursorY > 0) {
        this.clearRegion(0, 0,
                         this.terminalWidth, this.cursorY,
                         this.color, this.style);
      }
      this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
                       this.color, this.style);
      break;
    case 2: // Erase whole display
      this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
                       this.color, this.style);
      break;
    default:
      return;
    }
    this.needWrap = false;
  };
  
  VT100.prototype.csiK = function(number) {
    switch (number) {
    case 0: // Erase from cursor to end of line
      this.clearRegion(this.cursorX, this.cursorY,
                       this.terminalWidth - this.cursorX, 1,
                       this.color, this.style);
      break;
    case 1: // Erase from start of line to cursor
      this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
                       this.color, this.style);
      break;
    case 2: // Erase whole line
      this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
                       this.color, this.style);
      break;
    default:
      return;
    }
    this.needWrap = false;
  };
  
  VT100.prototype.csiL = function(number) {
    // Open line by inserting blank line(s)
    if (this.cursorY >= this.bottom) {
      return;
    }
    if (number == 0) {
      number = 1;
    }
    if (number > this.bottom - this.cursorY) {
      number = this.bottom - this.cursorY;
    }
    this.scrollRegion(0, this.cursorY,
                      this.terminalWidth, this.bottom - this.cursorY - number,
                      0, number, this.color, this.style);
    this.needWrap = false;
  };
  
  VT100.prototype.csiM = function(number) {
    // Delete line(s), scrolling up the bottom of the screen.
    if (this.cursorY >= this.bottom) {
      return;
    }
    if (number == 0) {
      number = 1;
    }
    if (number > this.bottom - this.cursorY) {
      number = this.bottom - this.cursorY;
    }
    this.scrollRegion(0, this.cursorY + number,
                      this.terminalWidth, this.bottom - this.cursorY - number,
                      0, -number, this.color, this.style);
    this.needWrap = false;
  };
  
  VT100.prototype.csim = function() {
    for (var i = 0; i <= this.npar; i++) {
      switch (this.par[i]) {
      case 0:
        this.attr         = 0x60F0 /* ATTR_DEFAULT */;
        this.attrFg       = false;
        this.attrBg       = false;
        break;
      case 1:  this.attr  = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */;         break;
      case 2:  this.attr  = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */;         break;
      case 4:  this.attr |= 0x0200 /* ATTR_UNDERLINE */;                              break;
      case 5:  this.attr |= 0x1000 /* ATTR_BLINK */;                                  break;
      case 7:  this.attr |= 0x0100 /* ATTR_REVERSE */;                                break;
      case 10:
        this.translate    = this.GMap[this.useGMap];
        this.dispCtrl     = false;
        this.toggleMeta   = false;
        break;
      case 11:
        this.translate    = this.CodePage437Map;
        this.dispCtrl     = true;
        this.toggleMeta   = false;
        break;
      case 12:
        this.translate    = this.CodePage437Map;
        this.dispCtrl     = true;
        this.toggleMeta   = true;
        break;
      case 21:
      case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */);                     break;
      case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */;                            break;
      case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */;                                break;
      case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */;                              break;
      case 38:
        if (this.npar >= (i+2) && this.par[i+1] == 5) {
          // Foreground color for extended color mode (256 colors). Escape code is formated
          // as: ESC 38; 5; 0-255. Last parameter is color code in range [0-255]. This is
          // not VT100 standard.
          this.attrFg     = (this.par[i+2] >= 0 && this.par[i+2] <= 255) ? this.par[i+2] : false;
          i              += 2;
        } else {
          // Default VT100 behaviour.
          this.attr       = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|0x0200 /* ATTR_UNDERLINE */ | 0x2000 /* ATTR_DEF_FG */;
        }
        break;
      case 39:
        this.attr         = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F)) | 0x2000 /* ATTR_DEF_FG */;
        this.attrFg       = false;
        break;
      case 48:
        if (this.npar >= (i+2) && this.par[i+1] == 5) {
          // Background color for extended color mode (256 colors). Escape code is formated
          // as: ESC 48; 5; 0-255. Last parameter is color code in range [0-255]. This is
          // not VT100 standard.
          this.attrBg     = (this.par[i+2] >= 0 && this.par[i+2] <= 255) ? this.par[i+2] : false;
          i              += 2;
        }
        break;
      case 49:
        this.attr        |= (0xF0|0x4000 /* ATTR_DEF_BG */);
        this.attrBg       = false;
        break;
      default:
        if (this.par[i] >= 30 && this.par[i] <= 37) {
            var fg        = this.par[i] - 30;
            this.attr     = ((this.attr & ~0x0F) | fg) & ~(0x2000 /* ATTR_DEF_FG */);
            this.attrFg   = false;
        } else if (this.par[i] >= 40 && this.par[i] <= 47) {
            var bg        = this.par[i] - 40;
            this.attr     = ((this.attr & ~0xF0) | (bg << 4)) & ~(0x4000 /* ATTR_DEF_BG */);
            this.attrBg   = false;
        }
        break;
      }
    }
    this.updateStyle();
  };
  
  VT100.prototype.csiP = function(number) {
    // Delete character(s) following cursor
    if (number == 0) {
      number = 1;
    }
    if (number > this.terminalWidth - this.cursorX) {
      number = this.terminalWidth - this.cursorX;
    }
    this.scrollRegion(this.cursorX + number, this.cursorY,
                      this.terminalWidth - this.cursorX - number, 1,
                      -number, 0, this.color, this.style);
    this.needWrap = false;
  };
  
  VT100.prototype.csiX = function(number) {
    // Clear characters following cursor
    if (number == 0) {
      number++;
    }
    if (number > this.terminalWidth - this.cursorX) {
      number = this.terminalWidth - this.cursorX;
    }
    this.clearRegion(this.cursorX, this.cursorY, number, 1,
                     this.color, this.style);
    this.needWrap = false;
  };
  
  VT100.prototype.settermCommand = function() {
    // Setterm commands are not implemented
  };
  
  VT100.prototype.doControl = function(ch) {
    if (this.printing) {
      this.sendControlToPrinter(ch);
      return '';
    }
    var lineBuf                = '';
    switch (ch) {
    case 0x00: /* ignored */                                              break;
    case 0x08: this.bs();                                                 break;
    case 0x09: this.ht();                                                 break;
    case 0x0A:
    case 0x0B:
    case 0x0C:
    case 0x84: this.lf(); if (!this.crLfMode)                             break;
    case 0x0D: this.cr();                                                 break;
    case 0x85: this.cr(); this.lf();                                      break;
    case 0x0E: this.useGMap     = 1;
               this.translate   = this.GMap[1];
               this.dispCtrl    = true;                                   break;
    case 0x0F: this.useGMap     = 0;
               this.translate   = this.GMap[0];
               this.dispCtrl    = false;                                  break;
    case 0x18:
    case 0x1A: this.isEsc       = 0 /* ESnormal */;                               break;
    case 0x1B: this.isEsc       = 1 /* ESesc */;                                  break;
    case 0x7F: /* ignored */                                              break;
    case 0x88: this.userTabStop[this.cursorX] = true;                     break;
    case 0x8D: this.ri();                                                 break;
    case 0x8E: this.isEsc       = 18 /* ESss2 */;                                  break;
    case 0x8F: this.isEsc       = 19 /* ESss3 */;                                  break;
    case 0x9A: this.respondID();                                          break;
    case 0x9B: this.isEsc       = 2 /* ESsquare */;                               break;
    case 0x07: if (this.isEsc != 17 /* EStitle */) {
                 this.beep();                                             break;
               }
               /* fall thru */
    default:   switch (this.isEsc) {
      case 1 /* ESesc */:
        this.isEsc              = 0 /* ESnormal */;
        switch (ch) {
  /*%*/ case 0x25: this.isEsc   = 13 /* ESpercent */;                              break;
  /*(*/ case 0x28: this.isEsc   = 8 /* ESsetG0 */;                                break;
  /*-*/ case 0x2D:
  /*)*/ case 0x29: this.isEsc   = 9 /* ESsetG1 */;                                break;
  /*.*/ case 0x2E:
  /***/ case 0x2A: this.isEsc   = 10 /* ESsetG2 */;                                break;
  /*/*/ case 0x2F:
  /*+*/ case 0x2B: this.isEsc   = 11 /* ESsetG3 */;                                break;
  /*#*/ case 0x23: this.isEsc   = 7 /* EShash */;                                 break;
  /*7*/ case 0x37: this.saveCursor();                                     break;
  /*8*/ case 0x38: this.restoreCursor();                                  break;
  /*>*/ case 0x3E: this.applKeyMode = false;                              break;
  /*=*/ case 0x3D: this.applKeyMode = true;                               break;
  /*D*/ case 0x44: this.lf();                                             break;
  /*E*/ case 0x45: this.cr(); this.lf();                                  break;
  /*M*/ case 0x4D: this.ri();                                             break;
  /*N*/ case 0x4E: this.isEsc   = 18 /* ESss2 */;                                  break;
  /*O*/ case 0x4F: this.isEsc   = 19 /* ESss3 */;                                  break;
  /*H*/ case 0x48: this.userTabStop[this.cursorX] = true;                 break;
  /*Z*/ case 0x5A: this.respondID();                                      break;
  /*[*/ case 0x5B: this.isEsc   = 2 /* ESsquare */;                               break;
  /*]*/ case 0x5D: this.isEsc   = 15 /* ESnonstd */;                               break;
  /*c*/ case 0x63: this.reset();                                          break;
  /*g*/ case 0x67: this.flashScreen();                                    break;
        default:                                                          break;
        }
        break;
      case 15 /* ESnonstd */:
        switch (ch) {
  /*0*/ case 0x30:
  /*1*/ case 0x31:
  /*2*/ case 0x32: this.isEsc   = 17 /* EStitle */; this.titleString = '';         break;
  /*P*/ case 0x50: this.npar    = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
                   this.isEsc   = 16 /* ESpalette */;                              break;
  /*R*/ case 0x52: // Palette support is not implemented
                   this.isEsc   = 0 /* ESnormal */;                               break;
        default:   this.isEsc   = 0 /* ESnormal */;                               break;
        }
        break;
      case 16 /* ESpalette */:
        if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
            (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
            (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
          this.par[this.npar++] = ch > 0x39  /*9*/ ? (ch & 0xDF) - 55
                                                  : (ch & 0xF);
          if (this.npar == 7) {
            // Palette support is not implemented
            this.isEsc          = 0 /* ESnormal */;
          }
        } else {
          this.isEsc            = 0 /* ESnormal */;
        }
        break;
      case 2 /* ESsquare */:
        this.npar               = 0;
        this.par                = [ 0, 0, 0, 0, 0, 0, 0, 0,
                                    0, 0, 0, 0, 0, 0, 0, 0 ];
        this.isEsc              = 3 /* ESgetpars */;
  /*[*/ if (ch == 0x5B) { // Function key
          this.isEsc            = 6 /* ESfunckey */;
          break;
        } else {
  /*?*/   this.isQuestionMark   = ch == 0x3F;
          if (this.isQuestionMark) {
            break;
          }
        }
        // Fall through
      case 5 /* ESdeviceattr */:
      case 3 /* ESgetpars */:
  /*;*/ if (ch == 0x3B) {
          this.npar++;
          break;
        } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
          var par               = this.par[this.npar];
          if (par == undefined) {
            par                 = 0;
          }
          this.par[this.npar]   = 10*par + (ch & 0xF);
          break;
        } else if (this.isEsc == 5 /* ESdeviceattr */) {
          switch (ch) {
  /*c*/   case 0x63: if (this.par[0] == 0) this.respondSecondaryDA();     break;
  /*m*/   case 0x6D: /* (re)set key modifier resource values */           break;
  /*n*/   case 0x6E: /* disable key modifier resource values */           break;
  /*p*/   case 0x70: /* set pointer mode resource value */                break;
          default:                                                        break;
          }
          this.isEsc            = 0 /* ESnormal */;
          break;
        } else {
          this.isEsc            = 4 /* ESgotpars */;
        }
        // Fall through
      case 4 /* ESgotpars */:
        this.isEsc              = 0 /* ESnormal */;
        if (this.isQuestionMark) {
          switch (ch) {
  /*h*/   case 0x68: this.setMode(true);                                  break;
  /*l*/   case 0x6C: this.setMode(false);                                 break;
  /*c*/   case 0x63: this.setCursorAttr(this.par[2], this.par[1]);        break;
          default:                                                        break;
          }
          this.isQuestionMark   = false;
          break;
        }
        switch (ch) {
  /*!*/ case 0x21: this.isEsc   = 12 /* ESbang */;                                 break;
  /*>*/ case 0x3E: if (!this.npar) this.isEsc  = 5 /* ESdeviceattr */;            break;
  /*G*/ case 0x47:
  /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY);            break;
  /*A*/ case 0x41: this.gotoXY(this.cursorX,
                               this.cursorY - (this.par[0] ? this.par[0] : 1));
                                                                          break;
  /*B*/ case 0x42:
  /*e*/ case 0x65: this.gotoXY(this.cursorX,
                               this.cursorY + (this.par[0] ? this.par[0] : 1));
                                                                          break;
  /*C*/ case 0x43:
  /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
                               this.cursorY);                             break;
  /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
                               this.cursorY);                             break;
  /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
                                                                          break;
  /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
                                                                          break;
  /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1);           break;
  /*H*/ case 0x48:
  /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1);        break;
  /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1);                break;
  /*at*/case 0x40: this.csiAt(this.par[0]);                               break;
  /*i*/ case 0x69: this.csii(this.par[0]);                                break;
  /*J*/ case 0x4A: this.csiJ(this.par[0]);                                break;
  /*K*/ case 0x4B: this.csiK(this.par[0]);                                break;
  /*L*/ case 0x4C: this.csiL(this.par[0]);                                break;
  /*M*/ case 0x4D: this.csiM(this.par[0]);                                break;
  /*m*/ case 0x6D: this.csim();                                           break;
  /*P*/ case 0x50: this.csiP(this.par[0]);                                break;
  /*X*/ case 0x58: this.csiX(this.par[0]);                                break;
  /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1);                break;
  /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1);                break;
  /*c*/ case 0x63: if (!this.par[0]) this.respondID();                    break;
  /*g*/ case 0x67: if (this.par[0] == 0) {
                     this.userTabStop[this.cursorX] = false;
                   } else if (this.par[0] == 2 || this.par[0] == 3) {
                     this.userTabStop               = [ ];
                     for (var i = 0; i < this.terminalWidth; i++) {
                       this.userTabStop[i]          = false;
                     }
                   }
                   break;
  /*h*/ case 0x68: this.setMode(true);                                    break;
  /*l*/ case 0x6C: this.setMode(false);                                   break;
  /*n*/ case 0x6E: switch (this.par[0]) {
                   case 5: this.statusReport();                           break;
                   case 6: this.cursorReport();                           break;
                   default:                                               break;
                   }
                   break;
  /*q*/ case 0x71: // LED control not implemented
                                                                          break;
  /*r*/ case 0x72: var t        = this.par[0] ? this.par[0] : 1;
                   var b        = this.par[1] ? this.par[1]
                                              : this.terminalHeight;
                   if (t < b && b <= this.terminalHeight) {
                     this.top   = t - 1;
                     this.bottom= b;
                     this.gotoXaY(0, 0);
                   }
                   break;
  /*b*/ case 0x62: var c        = this.par[0] ? this.par[0] : 1;
                   if (c > this.terminalWidth * this.terminalHeight) {
                     c          = this.terminalWidth * this.terminalHeight;
                   }
                   while (c-- > 0) {
                     lineBuf   += this.lastCharacter;
                   }
                   break;
  /*s*/ case 0x73: this.saveCursor();                                     break;
  /*u*/ case 0x75: this.restoreCursor();                                  break;
  /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1);                break;
  /*]*/ case 0x5D: this.settermCommand();                                 break;
        default:                                                          break;
        }
        break;
      case 12 /* ESbang */:
        if (ch == 'p') {
          this.reset();
        }
        this.isEsc              = 0 /* ESnormal */;
        break;
      case 13 /* ESpercent */:
        this.isEsc              = 0 /* ESnormal */;
        switch (ch) {
  /*at*/case 0x40: this.utfEnabled = false;                               break;
  /*G*/ case 0x47:
  /*8*/ case 0x38: this.utfEnabled = true;                                break;
        default:                                                          break;
        }
        break;
      case 6 /* ESfunckey */:
        this.isEsc              = 0 /* ESnormal */;                               break;
      case 7 /* EShash */:
        this.isEsc              = 0 /* ESnormal */;
  /*8*/ if (ch == 0x38) {
          // Screen alignment test not implemented
        }
        break;
      case 8 /* ESsetG0 */:
      case 9 /* ESsetG1 */:
      case 10 /* ESsetG2 */:
      case 11 /* ESsetG3 */:
        var g                   = this.isEsc - 8 /* ESsetG0 */;
        this.isEsc              = 0 /* ESnormal */;
        switch (ch) {
  /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap;                  break;
  /*A*/ case 0x42:
  /*B*/ case 0x42: this.GMap[g] = this.Latin1Map;                         break;
  /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map;                    break;
  /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap;                   break;
        default:                                                          break;
        }
        if (this.useGMap == g) {
          this.translate        = this.GMap[g];
        }
        break;
      case 17 /* EStitle */:
        if (ch == 0x07) {
          if (this.titleString && this.titleString.charAt(0) == ';') {
            this.titleString    = this.titleString.substr(1);
            if (this.titleString != '') {
              this.titleString += ' - ';
            }
            this.titleString += 'Shell In A Box'
          }
          try {
            window.document.title = this.titleString;
          } catch (e) {
          }
          this.isEsc            = 0 /* ESnormal */;
        } else {
          this.titleString     += String.fromCharCode(ch);
        }
        break;
      case 18 /* ESss2 */:
      case 19 /* ESss3 */:
        if (ch < 256) {
            ch                  = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
                                           [this.toggleMeta ? (ch | 0x80) : ch];
          if ((ch & 0xFF00) == 0xF000) {
            ch                  = ch & 0xFF;
          } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
            this.isEsc         = 0 /* ESnormal */;                                break;
          }
        }
        this.lastCharacter      = String.fromCharCode(ch);
        lineBuf                += this.lastCharacter;
        this.isEsc              = 0 /* ESnormal */;                               break;
      default:
        this.isEsc              = 0 /* ESnormal */;                               break;
      }
      break;
    }
    return lineBuf;
  };
  
  VT100.prototype.renderString = function(s, showCursor) {
    if (this.printing) {
      this.sendToPrinter(s);
      if (showCursor) {
        this.showCursor();
      }
      return;
    }
  
    // We try to minimize the number of DOM operations by coalescing individual
    // characters into strings. This is a significant performance improvement.
    var incX = s.length;
    if (incX > this.terminalWidth - this.cursorX) {
      incX   = this.terminalWidth - this.cursorX;
      if (incX <= 0) {
        return;
      }
      s      = s.substr(0, incX - 1) + s.charAt(s.length - 1);
    }
    if (showCursor) {
      // Minimize the number of calls to putString(), by avoiding a direct
      // call to this.showCursor()
      this.cursor.style.visibility = '';
    }
    this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
  };
  
  VT100.prototype.vt100 = function(s) {
    this.cursorNeedsShowing = this.hideCursor();
    this.respondString      = '';
    var lineBuf             = '';
    for (var i = 0; i < s.length; i++) {
      var ch = s.charCodeAt(i);
      if (this.utfEnabled) {
        // Decode UTF8 encoded character
        if (ch > 0x7F) {
          if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
            this.utfChar    = (this.utfChar << 6) | (ch & 0x3F);
            if (--this.utfCount <= 0) {
              if (this.utfChar > 0xFFFF || this.utfChar < 0) {
                ch = 0xFFFD;
              } else {
                ch          = this.utfChar;
              }
            } else {
              continue;
            }
          } else {
            if ((ch & 0xE0) == 0xC0) {
              this.utfCount = 1;
              this.utfChar  = ch & 0x1F;
            } else if ((ch & 0xF0) == 0xE0) {
              this.utfCount = 2;
              this.utfChar  = ch & 0x0F;
            } else if ((ch & 0xF8) == 0xF0) {
              this.utfCount = 3;
              this.utfChar  = ch & 0x07;
            } else if ((ch & 0xFC) == 0xF8) {
              this.utfCount = 4;
              this.utfChar  = ch & 0x03;
            } else if ((ch & 0xFE) == 0xFC) {
              this.utfCount = 5;
              this.utfChar  = ch & 0x01;
            } else {
              this.utfCount = 0;
            }
            continue;
          }
        } else {
          this.utfCount     = 0;
        }
      }
      var isNormalCharacter =
        (ch >= 32 && ch <= 127 || ch >= 160 ||
         this.utfEnabled && ch >= 128 ||
         !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
        (ch != 0x7F || this.dispCtrl);
  
      if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
        if (ch < 256) {
          ch                = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
        }
        if ((ch & 0xFF00) == 0xF000) {
          ch                = ch & 0xFF;
        } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
          continue;
        }
        if (!this.printing) {
          if (this.needWrap || this.insertMode) {
            if (lineBuf) {
              this.renderString(lineBuf);
              lineBuf       = '';
            }
          }
          if (this.needWrap) {
            this.cr(); this.lf();
          }
          if (this.insertMode) {
            this.scrollRegion(this.cursorX, this.cursorY,
                              this.terminalWidth - this.cursorX - 1, 1,
                              1, 0, this.color, this.style);
          }
        }
        this.lastCharacter  = String.fromCharCode(ch);
        lineBuf            += this.lastCharacter;
        if (!this.printing &&
            this.cursorX + lineBuf.length >= this.terminalWidth) {
          this.needWrap     = this.autoWrapMode;
        }
      } else {
        if (lineBuf) {
          this.renderString(lineBuf);
          lineBuf           = '';
        }
        var expand          = this.doControl(ch);
        if (expand.length) {
          var r             = this.respondString;
          this.respondString= r + this.vt100(expand);
        }
      }
    }
    if (lineBuf) {
      this.renderString(lineBuf, this.cursorNeedsShowing);
    } else if (this.cursorNeedsShowing) {
      this.showCursor();
    }
    return this.respondString;
  };
  
  VT100.prototype.Latin1Map = [
  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
  0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
  0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
  0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
  0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
  0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
  0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
  0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
  0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
  0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
  0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
  0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
  0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
  0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
  0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
  0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
  0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
  0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
  0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
  0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
  0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
  0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
  0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
  0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
  0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
  0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
  0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
  ];
  
  VT100.prototype.VT100GraphicsMap = [
  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
  0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
  0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
  0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
  0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
  0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
  0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
  0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
  0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
  0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
  0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
  0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
  0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
  0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
  0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
  0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
  0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
  0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
  0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
  0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
  0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
  0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
  0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
  0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
  0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
  0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
  0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
  0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
  0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
  ];
  
  VT100.prototype.CodePage437Map = [
  0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
  0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
  0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
  0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
  0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
  0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
  0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
  0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
  0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
  0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
  0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
  0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
  0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
  0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
  0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
  0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
  0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
  0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
  0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
  0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
  0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
  0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
  0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
  0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
  0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
  0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
  0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
  ];
  
  VT100.prototype.DirectToFontMap = [
  0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
  0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
  0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
  0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
  0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
  0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
  0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
  0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
  0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
  0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
  0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
  0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
  0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
  0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
  0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
  0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
  0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
  0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
  0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
  0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
  0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
  0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
  0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
  0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
  0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
  0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
  0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
  0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
  0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
  0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
  0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
  0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
  ];
  
  VT100.prototype.ctrlAction = [
    true,  false, false, false, false, false, false, true,
    true,  true,  true,  true,  true,  true,  true,  true,
    false, false, false, false, false, false, false, false,
    true,  false, true,  true,  false, false, false, false
  ];
  
  VT100.prototype.ctrlAlways = [
    true,  false, false, false, false, false, false, false,
    true,  false, true,  false, true,  true,  true,  true,
    false, false, false, false, false, false, false, false,
    false, false, false, true,  false, false, false, false
  ];
  
  /* vim: set filetype=javascript : */
  // ShellInABox.js -- Use XMLHttpRequest to provide an AJAX terminal emulator.
  // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
  //
  // This program is free software; you can redistribute it and/or modify
  // it under the terms of the GNU General Public License version 2 as
  // published by the Free Software Foundation.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along
  // with this program; if not, write to the Free Software Foundation, Inc.,
  // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  //
  // In addition to these license terms, the author grants the following
  // additional rights:
  //
  // If you modify this program, or any covered work, by linking or
  // combining it with the OpenSSL project's OpenSSL library (or a
  // modified version of that library), containing parts covered by the
  // terms of the OpenSSL or SSLeay licenses, the author
  // grants you additional permission to convey the resulting work.
  // Corresponding Source for a non-source form of such a combination
  // shall include the source code for the parts of OpenSSL used as well
  // as that of the covered work.
  //
  // You may at your option choose to remove this additional permission from
  // the work, or from any part of it.
  //
  // It is possible to build this program in a way that it loads OpenSSL
  // libraries at run-time. If doing so, the following notices are required
  // by the OpenSSL and SSLeay licenses:
  //
  // This product includes software developed by the OpenSSL Project
  // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
  //
  // This product includes cryptographic software written by Eric Young
  // (eay@cryptsoft.com)
  //
  //
  // The most up-to-date version of this program is always available from
  // http://shellinabox.com
  //
  //
  // Notes:
  //
  // The author believes that for the purposes of this license, you meet the
  // requirements for publishing the source code, if your web server publishes
  // the source in unmodified form (i.e. with licensing information, comments,
  // formatting, and identifier names intact). If there are technical reasons
  // that require you to make changes to the source code when serving the
  // JavaScript (e.g to remove pre-processor directives from the source), these
  // changes should be done in a reversible fashion.
  //
  // The author does not consider websites that reference this script in
  // unmodified form, and web servers that serve this script in unmodified form
  // to be derived works. As such, they are believed to be outside of the
  // scope of this license and not subject to the rights or restrictions of the
  // GNU General Public License.
  //
  // If in doubt, consult a legal professional familiar with the laws that
  // apply in your country.
  
  // #define XHR_UNITIALIZED 0
  // #define XHR_OPEN        1
  // #define XHR_SENT        2
  // #define XHR_RECEIVING   3
  // #define XHR_LOADED      4
  
  // IE does not define XMLHttpRequest by default, so we provide a suitable
  // wrapper.
  if (typeof XMLHttpRequest == 'undefined') {
    XMLHttpRequest = function() {
      try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch (e) { }
      try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch (e) { }
      try { return new ActiveXObject('Msxml2.XMLHTTP');    } catch (e) { }
      try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { }
      throw new Error('');
    };
  }
  
  function extend(subClass, baseClass) {
    function inheritance() { }
    inheritance.prototype          = baseClass.prototype;
    subClass.prototype             = new inheritance();
    subClass.prototype.constructor = subClass;
    subClass.prototype.superClass  = baseClass.prototype;
  };
  
  function ShellInABox(url, container) {
    if (url == undefined) {
      this.rooturl    = document.location.href;
      this.url        = document.location.href.replace(/[?#].*/, '');
    } else {
      this.rooturl    = url;
      this.url        = url;
    }
    if (document.location.hash != '') {
      var hash        = decodeURIComponent(document.location.hash).
                        replace(/^#/, '');
      this.nextUrl    = hash.replace(/,.*/, '');
      this.session    = hash.replace(/[^,]*,/, '');
    } else {
      this.nextUrl    = this.url;
      this.session    = null;
    }
    this.pendingKeys  = '';
    this.keysInFlight = false;
    this.connected    = false;
    this.replayOnOutput  = false;
    this.replayOnSession = false;
    this.superClass.constructor.call(this, container);
  
  
    // We have to initiate the first XMLHttpRequest from a timer. Otherwise,
    // Chrome never realizes that the page has loaded.
    setTimeout(function(shellInABox) {
                 return function() {
                   shellInABox.messageInit();
                   shellInABox.sendRequest();
                 };
               }(this), 1);
  };
  extend(ShellInABox, VT100);
  
  ShellInABox.prototype.sessionClosed = function() {
    try {
      this.connected    = false;
      if (this.session) {
        this.session    = undefined;
        if (this.cursorX > 0) {
          this.vt100('\r\n');
        }
        this.vt100('Session closed.');
      }
      this.showReconnect(true);
      if (this.replayOnSession) {
        this.messageReplay('session', 'closed');
      }
    } catch (e) {
    }
  };
  
  ShellInABox.prototype.reconnect = function() {
    this.showReconnect(false);
    if (!this.session) {
      if (document.location.hash != '') {
        // A shellinaboxd daemon launched from a CGI only allows a single
        // session. In order to reconnect, we must reload the frame definition
        // and obtain a new port number. As this is a different origin, we
        // need to get enclosing page to help us.
        parent.location        = this.nextUrl;
      } else {
        if (this.url != this.nextUrl) {
          document.location.replace(this.nextUrl);
        } else {
          this.pendingKeys     = '';
          this.keysInFlight    = false;
          this.reset(true);
          this.sendRequest();
        }
      }
    }
    return false;
  };
  
  ShellInABox.prototype.sendRequest = function(request) {
    if (request == undefined) {
      request                  = new XMLHttpRequest();
    }
    request.open('POST', this.url + '?', true);
    request.timeout = 30000; // Don't leave POST pending forever: force 30s timeout to prevent HTTP Proxy thread hijack
    request.setRequestHeader('Cache-Control', 'no-cache');
    request.setRequestHeader('Content-Type',
                             'application/x-www-form-urlencoded; charset=utf-8');
    var content                = 'width=' + this.terminalWidth +
                                 '&height=' + this.terminalHeight +
                                 (this.session ? '&session=' +
                                  encodeURIComponent(this.session) : '&rooturl='+
                                  encodeURIComponent(this.rooturl));
  
    request.onreadystatechange = function(shellInABox) {
      return function() {
               try {
                 return shellInABox.onReadyStateChange(request);
               } catch (e) {
                 shellInABox.sessionClosed();
               }
             }
      }(this);
    request.send(content);
  };
  
  ShellInABox.prototype.onReadyStateChange = function(request) {
    if (request.readyState == 4 /* XHR_LOADED */) {
      if (request.status == 200) {
        this.connected = true;
        var response   = eval('(' + request.responseText + ')');
        if (response.data) {
          if (this.replayOnOutput) {
            this.messageReplay('output', response.data);
          }
          this.vt100(response.data);
        }
  
        if (!response.session ||
            this.session && this.session != response.session) {
          this.sessionClosed();
        } else {
          if (this.replayOnSession && !this.session && response.session) {
            this.messageReplay('session', 'alive');
          }
          this.session = response.session;
          this.sendRequest(request);
        }
      } else if (request.status == 0) {
        // Time Out or other connection problems: retry after 1s to prevent release CPU before retry
        setTimeout(function(shellInABox) {
          return function() { 
            shellInABox.sendRequest();
          };
        }(this), 1000);
      } else {
        this.sessionClosed();
      }
    }
  };
  
  ShellInABox.prototype.sendKeys = function(keys) {
    if (!this.connected) {
      return;
    }
    if (this.keysInFlight || this.session == undefined) {
      this.pendingKeys          += keys;
    } else {
      this.keysInFlight          = true;
      keys                       = this.pendingKeys + keys;
      this.pendingKeys           = '';
      var request                = new XMLHttpRequest();
      request.open('POST', this.url + '?', true);
      request.setRequestHeader('Cache-Control', 'no-cache');
      request.setRequestHeader('Content-Type',
                             'application/x-www-form-urlencoded; charset=utf-8');
      var content                = 'width=' + this.terminalWidth +
                                   '&height=' + this.terminalHeight +
                                   '&session=' +encodeURIComponent(this.session)+
                                   '&keys=' + encodeURIComponent(keys);
      request.onreadystatechange = function(shellInABox) {
        return function() {
                 try {
                   return shellInABox.keyPressReadyStateChange(request);
                 } catch (e) {
                 }
               }
        }(this);
      request.send(content);
    }
  };
  
  ShellInABox.prototype.keyPressReadyStateChange = function(request) {
    if (request.readyState == 4 /* XHR_LOADED */) {
      this.keysInFlight = false;
      if (this.pendingKeys) {
        this.sendKeys('');
      }
    }
  };
  
  ShellInABox.prototype.keysPressed = function(ch) {
    var hex = '0123456789ABCDEF';
    var s   = '';
    for (var i = 0; i < ch.length; i++) {
      var c = ch.charCodeAt(i);
      if (c < 128) {
        s += hex.charAt(c >> 4) + hex.charAt(c & 0xF);
      } else if (c < 0x800) {
        s += hex.charAt(0xC +  (c >> 10)       ) +
             hex.charAt(       (c >>  6) & 0xF ) +
             hex.charAt(0x8 + ((c >>  4) & 0x3)) +
             hex.charAt(        c        & 0xF );
      } else if (c < 0x10000) {
        s += 'E'                                 +
             hex.charAt(       (c >> 12)       ) +
             hex.charAt(0x8 + ((c >> 10) & 0x3)) +
             hex.charAt(       (c >>  6) & 0xF ) +
             hex.charAt(0x8 + ((c >>  4) & 0x3)) +
             hex.charAt(        c        & 0xF );
      } else if (c < 0x110000) {
        s += 'F'                                 +
             hex.charAt(       (c >> 18)       ) +
             hex.charAt(0x8 + ((c >> 16) & 0x3)) +
             hex.charAt(       (c >> 12) & 0xF ) +
             hex.charAt(0x8 + ((c >> 10) & 0x3)) +
             hex.charAt(       (c >>  6) & 0xF ) +
             hex.charAt(0x8 + ((c >>  4) & 0x3)) +
             hex.charAt(        c        & 0xF );
      }
    }
    this.sendKeys(s);
  };
  
  ShellInABox.prototype.resized = function(w, h) {
    // Do not send a resize request until we are fully initialized.
    if (this.session) {
      // sendKeys() always transmits the current terminal size. So, flush all
      // pending keys.
      this.sendKeys('');
    }
  };
  
  ShellInABox.prototype.toggleSSL = function() {
    if (document.location.hash != '') {
      if (this.nextUrl.match(/\?plain$/)) {
        this.nextUrl    = this.nextUrl.replace(/\?plain$/, '');
      } else {
        this.nextUrl    = this.nextUrl.replace(/[?#].*/, '') + '?plain';
      }
      if (!this.session) {
        parent.location = this.nextUrl;
      }
    } else {
      this.nextUrl      = this.nextUrl.match(/^https:/)
             ? this.nextUrl.replace(/^https:/, 'http:').replace(/\/*$/, '/plain')
             : this.nextUrl.replace(/^http/, 'https').replace(/\/*plain$/, '');
    }
    if (this.nextUrl.match(/^[:]*:\/\/[^/]*$/)) {
      this.nextUrl     += '/';
    }
    if (this.session && this.nextUrl != this.url) {
      alert('This change will take effect the next time you login.');
    }
  };
  
  ShellInABox.prototype.extendContextMenu = function(entries, actions) {
    // Modify the entries and actions in place, adding any locally defined
    // menu entries.
    var oldActions            = [ ];
    for (var i = 0; i < actions.length; i++) {
      oldActions[i]           = actions[i];
    }
    for (var node = entries.firstChild, i = 0, j = 0; node;
         node = node.nextSibling) {
      if (node.tagName == 'LI') {
        actions[i++]          = oldActions[j++];
        if (node.id == "endconfig") {
          node.id             = '';
          if (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL &&
              !(typeof disableSSLMenu != 'undefined' && disableSSLMenu)) {
            // If the server supports both SSL and plain text connections,
            // provide a menu entry to switch between the two.
            var newNode       = document.createElement('li');
            var isSecure;
            if (document.location.hash != '') {
              isSecure        = !this.nextUrl.match(/\?plain$/);
            } else {
              isSecure        =  this.nextUrl.match(/^https:/);
            }
            newNode.innerHTML = (isSecure ? '&#10004; ' : '') + 'Secure';
            if (node.nextSibling) {
              entries.insertBefore(newNode, node.nextSibling);
            } else {
              entries.appendChild(newNode);
            }
            actions[i++]      = this.toggleSSL;
            node              = newNode;
          }
          node.id             = 'endconfig';
        }
      }
    }
  
  };
  
  ShellInABox.prototype.messageInit = function() {
  
    // Test if server option for iframe message passing was set.
    if (!serverMessagesOrigin) {
      return;
    }
  
    // Test for browser support of this feature. JSON class functionality is
    // also needed because some older IE browsers, support only string passing
    // and we don't want to use unsafe eval() function in this case.
    if (!window.postMessage || !window.JSON ||
        !window.JSON.parse  || !window.JSON.stringify) {
      return;
    }
  
    // Install event listener.
    if (window.addEventListener) {
      window.addEventListener('message', function(shellInABox) {
        return function(message) {
          shellInABox.messageReceive(message);
        }
      }(this), false);
    } else {
      // For IE8
      if (window.attachEvent) {
        window.attachEvent('onmessage', function(shellInABox) {
          return function(message) {
            shellInABox.messageReceive(message);
          }
        }(this));
      }
    }
  
  };
  
  ShellInABox.prototype.messageReceive = function (message) {
  
    // Check for message origin if needed.
    if (serverMessagesOrigin !== "*") {
      if (serverMessagesOrigin !== message.origin) {
        return;
      }
    }
  
    // Remember replay information.
    if (!this.replaySource || !this.replayOrigin) {
      this.replaySource   = message.source;
      this.replayOrigin   = message.origin;
    }
  
    // Handle received message.
    var decoded           = JSON.parse(message.data);
    switch (decoded.type) {
    case 'input'  :
      // Input received data to terminal.
      this.keysPressed(decoded.data);
      break;
    case 'output' :
      // Enable, disable or toggle passing terminal output to parent window.
      switch (decoded.data) {
      case 'enable'  : this.replayOnOutput = true;                 break;
      case 'disable' : this.replayOnOutput = false;                break;
      case 'toggle'  : this.replayOnOutput = !this.replayOnOutput; break;
      }
      break;
    case 'session':
      // Replay with session status.
      this.messageReplay('session', this.session ? 'alive' : 'closed');
      break;
    case 'onsessionchange':
      // Enable, disable or toggle passing session status to parent window.
      switch (decoded.data) {
      case 'enable'  : this.replayOnSession = true;                  break;
      case 'disable' : this.replayOnSession = false;                 break;
      case 'toggle'  : this.replayOnSession = !this.replayOnSession; break;
      }
      break;
    case 'reconnect':
      this.reconnect();
      break;
    }
  };
  
  ShellInABox.prototype.messageReplay = function(type, data) {
    if (this.replaySource && this.replayOrigin) {
      var encoded = JSON.stringify({ type : type, data : data });
      this.replaySource.postMessage(encoded, this.replayOrigin);
    }
  };
  
  ShellInABox.prototype.about = function() {
    alert("Shell In A Box " + "2.18  (revision c875886)" +
          "\n\n" +
          "Copyright 2008-2015 by Markus Gutschke. For more information visit\n" +
          "http://shellinabox.com or http://github.com/shellinabox/." +
          (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
           "\n\n" +
           "This product includes software developed by the OpenSSL Project for\n" +
           "use in the OpenSSL Toolkit. (http://www.openssl.org/)" +
           "\n\n" +
           "This product includes cryptographic software written by Eric Young\n" +
           "(eay@cryptsoft.com)" :
           ""));
  };
  
  /* vim: set filetype=javascript : */
  